diff --git a/.changeset/update-effect-rc-115.md b/.changeset/update-effect-rc-115.md new file mode 100644 index 000000000..2dd860a21 --- /dev/null +++ b/.changeset/update-effect-rc-115.md @@ -0,0 +1,6 @@ +--- +"effect-app": patch +"@effect-app/cli": patch +--- + +Upgrade Effect packages to npm `4.0.0-rc.115`. Drop the pkg.pr.new pin used while `effect@4.0.0-rc.114` was unpublished. diff --git a/package.json b/package.json index 4e2764527..9f20a3d54 100644 --- a/package.json +++ b/package.json @@ -50,15 +50,15 @@ "@effect-app/infra": "workspace:*", "@effect/language-service": "0.87.2", "@effect/tsgo": "^0.45.0", - "@effect/platform-node": "4.0.0-rc.114", - "@effect/vitest": "4.0.0-rc.114", + "@effect/platform-node": "4.0.0-rc.115", + "@effect/vitest": "4.0.0-rc.115", "@tsconfig/strictest": "^2.0.8", "@types/node": "25.9.1", "@typescript-eslint/eslint-plugin": "8.60.0", "@typescript-eslint/parser": "8.60.0", "@typescript/native-preview": "7.0.0-dev.20260626.1", "dprint": "^0.54.0", - "effect": "4.0.0-rc.114", + "effect": "4.0.0-rc.115", "effect-app": "workspace:*", "eslint": "^10.4.1", "json5": "^2.2.3", diff --git a/packages/cli/package.json b/packages/cli/package.json index 15b1c95d6..043ec0c14 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -14,8 +14,8 @@ "effect-app-cli": "./bin.js" }, "dependencies": { - "@effect/platform-node": "4.0.0-rc.114", - "effect": "4.0.0-rc.114", + "@effect/platform-node": "4.0.0-rc.115", + "effect": "4.0.0-rc.115", "js-yaml": "4.2.0" }, "devDependencies": { diff --git a/packages/e2e/package.json b/packages/e2e/package.json index d5c6483fb..e90cb88a3 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -10,13 +10,13 @@ "effect-app": "workspace:*" }, "devDependencies": { - "@effect/atom-vue": "^4.0.0-rc.114", - "@effect/platform-node": "4.0.0-rc.114", - "@effect/vitest": "4.0.0-rc.114", + "@effect/atom-vue": "^4.0.0-rc.115", + "@effect/platform-node": "4.0.0-rc.115", + "@effect/vitest": "4.0.0-rc.115", "@tanstack/vue-query": "5.96.2", "@types/node": "25.9.1", "@vitejs/plugin-vue": "^6.0.7", - "effect": "^4.0.0-rc.114", + "effect": "^4.0.0-rc.115", "typescript": "~6.0.3", "vitest": "^4.1.7", "vue": "^3.5.35" diff --git a/packages/effect-app/package.json b/packages/effect-app/package.json index 1ad7c0881..351073f89 100644 --- a/packages/effect-app/package.json +++ b/packages/effect-app/package.json @@ -26,7 +26,7 @@ "vitest": "^4.1.7" }, "peerDependencies": { - "effect": "^4.0.0-rc.114" + "effect": "^4.0.0-rc.115" }, "typesVersions": { "*": { diff --git a/packages/infra/package.json b/packages/infra/package.json index 8757c3c3f..a2b65d755 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -21,7 +21,7 @@ "devDependencies": { "@azure/cosmos": "^4.9.3", "@azure/service-bus": "^7.9.5", - "@effect/sql-sqlite-node": "4.0.0-rc.114", + "@effect/sql-sqlite-node": "4.0.0-rc.115", "@sentry/opentelemetry": "10.55.0", "@types/better-sqlite3": "^7.6.13", "@types/node": "25.9.1", @@ -39,12 +39,12 @@ "peerDependencies": { "@azure/cosmos": "^4.9.3", "@azure/service-bus": "^7.9.5", - "@effect/vitest": "^4.0.0-rc.114", + "@effect/vitest": "^4.0.0-rc.115", "@sendgrid/helpers": "^8.0.0", "@sendgrid/mail": "^8.1.6", "@sentry/node": "10.55.0", "@sentry/opentelemetry": "10.55.0", - "effect": "^4.0.0-rc.114", + "effect": "^4.0.0-rc.115", "jwt-decode": "^4.0.0", "redis": "^3.1.2", "redlock": "^4.2.0" diff --git a/packages/vue-components/package.json b/packages/vue-components/package.json index 2504ee19a..1b6172664 100644 --- a/packages/vue-components/package.json +++ b/packages/vue-components/package.json @@ -25,7 +25,7 @@ }, "peerDependencies": { "@mdi/js": "^7.4.47", - "effect": "^4.0.0-rc.114", + "effect": "^4.0.0-rc.115", "intl-messageformat": "^11.2.7", "mdi-js": "^1.0.1", "primeflex": "^4.0.0", diff --git a/packages/vue/package.json b/packages/vue/package.json index 20f75b64f..89290446c 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -18,14 +18,14 @@ "query-string": "^9.4.0" }, "peerDependencies": { - "@effect/atom-vue": "^4.0.0-rc.114", - "@effect/platform-browser": "^4.0.0-rc.114", + "@effect/atom-vue": "^4.0.0-rc.115", + "@effect/platform-browser": "^4.0.0-rc.115", "@sentry/browser": "^10.55.0", - "effect": "^4.0.0-rc.114", + "effect": "^4.0.0-rc.115", "vue": "^3.5.35" }, "devDependencies": { - "@effect/vitest": "4.0.0-rc.114", + "@effect/vitest": "4.0.0-rc.115", "@formatjs/icu-messageformat-parser": "^3.5.10", "@types/node": "25.9.1", "@vitejs/plugin-vue": "^6.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fc8b5b29..d968a10dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,6 @@ settings: overrides: date-fns: ^4.4.0 - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 fast-check: ^4.9.0 vue: ^3.5.35 @@ -46,14 +45,14 @@ importers: specifier: 0.87.2 version: 0.87.2 "@effect/platform-node": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(redis@6.2.1(@opentelemetry/api@1.9.1)) "@effect/tsgo": specifier: ^0.45.0 version: 0.45.0 "@effect/vitest": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@tsconfig/strictest": specifier: ^2.0.8 version: 2.0.8 @@ -73,8 +72,8 @@ importers: specifier: ^0.54.0 version: 0.54.0 effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115 effect-app: specifier: workspace:* version: link:packages/effect-app @@ -118,11 +117,11 @@ importers: packages/cli: dependencies: "@effect/platform-node": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(redis@6.2.1(@opentelemetry/api@1.9.1)) effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115 js-yaml: specifier: 4.2.0 version: 4.2.0 @@ -154,14 +153,14 @@ importers: version: link:../effect-app devDependencies: "@effect/atom-vue": - specifier: ^4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) "@effect/platform-node": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(redis@6.2.1(@opentelemetry/api@1.9.1)) "@effect/vitest": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@tanstack/vue-query": specifier: 5.96.2 version: 5.96.2(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) @@ -172,8 +171,8 @@ importers: specifier: ^6.0.7 version: 6.0.7(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115 typescript: specifier: ~6.0.3 version: 6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e) @@ -193,8 +192,8 @@ importers: specifier: ^4.4.0 version: 4.4.0 effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115 nanoid: specifier: ^5.1.11 version: 5.1.11 @@ -332,8 +331,8 @@ importers: packages/infra: dependencies: "@effect/vitest": - specifier: ^4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@faker-js/faker": specifier: ^8.4.1 version: 8.4.1 @@ -347,8 +346,8 @@ importers: specifier: 10.55.0 version: 10.55.0(supports-color@8.1.1) effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115 effect-app: specifier: workspace:* version: link:../effect-app @@ -375,8 +374,8 @@ importers: specifier: ^7.9.5 version: 7.9.5(supports-color@8.1.1) "@effect/sql-sqlite-node": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115) "@sentry/opentelemetry": specifier: 10.55.0 version: 10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) @@ -421,11 +420,11 @@ importers: packages/vue: dependencies: "@effect/atom-vue": - specifier: ^4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) "@effect/platform-browser": - specifier: ^4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4) + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115) "@formatjs/intl": specifier: ^4.1.12 version: 4.1.12 @@ -442,8 +441,8 @@ importers: specifier: ^5.4.4 version: 5.4.4 effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115 effect-app: specifier: workspace:* version: link:../effect-app @@ -455,8 +454,8 @@ importers: version: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) devDependencies: "@effect/vitest": - specifier: 4.0.0-rc.114 - version: 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-rc.115 + version: 4.0.0-rc.115(effect@4.0.0-rc.115)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@formatjs/icu-messageformat-parser": specifier: ^3.5.10 version: 3.5.10 @@ -495,8 +494,8 @@ importers: specifier: ^1.32.0 version: 1.32.0(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) effect: - specifier: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 - version: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + specifier: ^4.0.0-rc.115 + version: 4.0.0-rc.115 effect-app: specifier: workspace:* version: link:../effect-app @@ -1189,13 +1188,12 @@ packages: } hasBin: true - "@effect/atom-vue@4.0.0-rc.114": + "@effect/atom-vue@4.0.0-rc.115": resolution: { - integrity: sha512-nVsD7QnJL1/zOXxIlAwBrjAeGp9ik47it43VXOgURRkXK+25rz+MJ0bM0QSZZGtdIOpEET610Hmrh1uIQOVE2A==, + integrity: sha512-1750Ozq5zxliHP78hbw80ynant7IuB6V1MKS+6dnhATZ5I1oViM1Y10ZsSdU9/aGnbnMfpN3SG2URwqhzorXmA==, } - version: 4.0.0-rc.114 peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 vue: ^3.5.35 "@effect/language-service@0.87.2": @@ -1204,59 +1202,53 @@ packages: } hasBin: true - "@effect/platform-browser@4.0.0-rc.114": + "@effect/platform-browser@4.0.0-rc.115": resolution: { - integrity: sha512-QlE2aKZyjg6Rn7OYs3hZ2k+2ei+CI/wtsZ1eZQr5SR4b06Vqb/At0v/sliGGMkp3S8zII3Y87dkmENWrxSqlRw==, + integrity: sha512-RwpnxRYSBHBL1HYYlRwIrwz0+1hwmn5RvgqkKI0Tb+ChOIR9UCMpXsAhnwQcGP+mBidrC7dlloL42poGHZW4yw==, } - version: 4.0.0-rc.114 peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 "@effect/platform-node-shared@4.0.0-beta.86": resolution: { integrity: sha512-QNixdacCFZ001duRVZTIRUa6BS1YkU9cbC8yN7NnAMc0upUiIuLI6YO9jKMG3HetW1MjZ3PeSOzMM6nneUYPow==, } - version: 4.0.0-beta.86 engines: { node: ">=18.0.0" } peerDependencies: effect: ^4.0.0-beta.86 - "@effect/platform-node-shared@4.0.0-rc.114": + "@effect/platform-node-shared@4.0.0-rc.115": resolution: { - integrity: sha512-YVoL71bJpa8HmAZl63nvVgOI4UBg61Z+6U0qnNN2ZlUktxG7eu26Y/puRxEtMxivxEUHR5vKJsuqQ3PRK4fLQA==, + integrity: sha512-rcBwhDIfb82akJoTv7Vwh8K5x9vC/XfuePS7JjOr+z+dORXzsmKqI1BUQF27ARCN9fTAbDh7I6YvBlWhLjnqNg==, } - version: 4.0.0-rc.114 engines: { node: ">=18.0.0" } peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 "@effect/platform-node@4.0.0-beta.86": resolution: { integrity: sha512-K9omw3cICZAHV4caMD9ZVixmnMYZ97+5A8d11orza0VcJiSLoHmwiC9X9nxL+mAcFIUTMvKt4EVR7mCk5hbUzg==, } - version: 4.0.0-beta.86 engines: { node: ">=18.0.0" } peerDependencies: effect: ^4.0.0-beta.86 ioredis: ^5.7.0 - "@effect/platform-node@4.0.0-rc.114": + "@effect/platform-node@4.0.0-rc.115": resolution: { - integrity: sha512-eqPvK8XswUw+FVe7CVqiyYUz4NHPz/1DiNsXi8GwIrsMOLT2wLteaAUID4grjDX7XWK1ONUVy4gS9IwBvp+EjA==, + integrity: sha512-mW03Wjq2FiTL5oi9yOkaKpCyUNBaduKgTk7qMHNELvC21Jq2qOAfdsfH+p+s2zmpB9w2RSa+ApODv4WjO9wQfw==, } - version: 4.0.0-rc.114 engines: { node: ">=18.0.0" } peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 redis: ">=5.0.0 <7.0.0" - "@effect/sql-sqlite-node@4.0.0-rc.114": + "@effect/sql-sqlite-node@4.0.0-rc.115": resolution: { - integrity: sha512-2E2kWKi1dJxg87o5FqbhKfVJGs9qSmBAXC4riUJvPa+gNgGa38k9MmpAYJyJUw933Shh1sBE8urpsiZi4mt0sQ==, + integrity: sha512-53n3Kbwush7K/8/G1CDz638ClEoMvnUOT5BltCxMZ8TqcxeNoMv+nty08ZdrJ7P6m1q1RIeWlqdy5J9ZDICpbw==, } - version: 4.0.0-rc.114 peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 "@effect/tsgo-darwin-arm64@0.45.0": resolution: { @@ -1313,13 +1305,12 @@ packages: } hasBin: true - "@effect/vitest@4.0.0-rc.114": + "@effect/vitest@4.0.0-rc.115": resolution: { - integrity: sha512-UQcfWdp7akz7EUp/lqb9UPFG3cgb3yQHhBV77QvzvxuxjwX5gK658X+TjDvgP+7LHl2lAgj3aOCBbnaSapfe6g==, + integrity: sha512-DJR0hfdr3HNXAkzW8AzfvUz6VmrNFLrKk9tZGDURi33faMyxkc7ZewxELMMZwGpOTGN4mfqYVbX2CXgI+ZJBeQ==, } - version: 4.0.0-rc.114 peerDependencies: - effect: ^4.0.0-rc.114 + effect: ^4.0.0-rc.115 vitest: ">=5.0.0 <6.0.0" "@emnapi/core@1.10.0": @@ -2169,6 +2160,48 @@ packages: integrity: sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==, } + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4": + resolution: { + integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==, + } + cpu: [arm64] + os: [darwin] + + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4": + resolution: { + integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==, + } + cpu: [x64] + os: [darwin] + + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4": + resolution: { + integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==, + } + cpu: [arm64] + os: [linux] + + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4": + resolution: { + integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==, + } + cpu: [arm] + os: [linux] + + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4": + resolution: { + integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==, + } + cpu: [x64] + os: [linux] + + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4": + resolution: { + integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==, + } + cpu: [x64] + os: [win32] + "@napi-rs/wasm-runtime@1.1.4": resolution: { integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==, @@ -5701,12 +5734,15 @@ packages: engines: { node: ">=14" } hasBin: true - effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4: + effect@4.0.0-beta.86: resolution: { - integrity: sha512-3mUudXofhydQQAPOvd0w+N9HxQN2kLjjBfl0SXKRh9dMYBsayyO5z1W+CiB+gzuUAmtU8ppV/W5QRiHPGwV98A==, - tarball: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4, + integrity: sha512-+Y16hy3LhAju/FM3dz3Xp+aGi8dw9AtEzswcvwxREpeEPq+rL8TFS1PgWAO1+/3kKKH4bq9NdBGtfhHzM2K7Ow==, + } + + effect@4.0.0-rc.115: + resolution: { + integrity: sha512-ogYulZ5ffeOzrJqQrG0XOkDO5lKn2s9KNHhoxJy/6wKUkF0i12dlVXWA9TgsgQ+zV/JzizyG0+XMPepZEQX6vw==, } - version: 4.0.0-rc.114 electron-to-chromium@1.5.267: resolution: { @@ -6144,6 +6180,11 @@ packages: } engines: { node: ">=14.16" } + find-my-way-ts@0.1.6: + resolution: { + integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==, + } + find-up@4.1.0: resolution: { integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, @@ -6547,6 +6588,12 @@ packages: integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, } + ini@7.0.0: + resolution: { + integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==, + } + engines: { node: ^22.22.2 || ^24.15.0 || >=26.0.0 } + internal-slot@1.1.0: resolution: { integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, @@ -6985,6 +7032,11 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, } + kubernetes-types@1.30.0: + resolution: { + integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==, + } + levn@0.4.1: resolution: { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, @@ -7432,11 +7484,27 @@ packages: integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, } + msgpackr-extract@3.0.4: + resolution: { + integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==, + } + hasBin: true + + msgpackr@2.1.0: + resolution: { + integrity: sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ==, + } + muggle-string@0.4.1: resolution: { integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==, } + multipasta@0.2.8: + resolution: { + integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==, + } + nanoid@3.3.12: resolution: { integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==, @@ -7482,6 +7550,12 @@ packages: integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==, } + node-gyp-build-optional-packages@5.2.2: + resolution: { + integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==, + } + hasBin: true + node-releases@2.0.27: resolution: { integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==, @@ -8861,6 +8935,12 @@ packages: integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==, } + toml@4.3.0: + resolution: { + integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==, + } + engines: { node: ">=20" } + tough-cookie@6.0.1: resolution: { integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==, @@ -9120,6 +9200,12 @@ packages: integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==, } + uuid@14.0.2: + resolution: { + integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==, + } + hasBin: true + validator@13.15.35: resolution: { integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==, @@ -10225,47 +10311,47 @@ snapshots: "@effect-app/cli@2.1.0-beta.35(ioredis@5.9.3(supports-color@8.1.1))": dependencies: - "@effect/platform-node": 4.0.0-beta.86(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(ioredis@5.9.3(supports-color@8.1.1)) - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + "@effect/platform-node": 4.0.0-beta.86(effect@4.0.0-beta.86)(ioredis@5.9.3(supports-color@8.1.1)) + effect: 4.0.0-beta.86 js-yaml: 4.2.0 transitivePeerDependencies: - bufferutil - ioredis - utf-8-validate - "@effect/atom-vue@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": + "@effect/atom-vue@4.0.0-rc.115(effect@4.0.0-rc.115)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": dependencies: - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-rc.115 vue: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) "@effect/language-service@0.87.2": {} - "@effect/platform-browser@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)": + "@effect/platform-browser@4.0.0-rc.115(effect@4.0.0-rc.115)": dependencies: - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-rc.115 - "@effect/platform-node-shared@4.0.0-beta.86(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)": + "@effect/platform-node-shared@4.0.0-beta.86(effect@4.0.0-beta.86)": dependencies: "@types/ws": 8.18.1 - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-beta.86 ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/platform-node-shared@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)": + "@effect/platform-node-shared@4.0.0-rc.115(effect@4.0.0-rc.115)": dependencies: "@types/ws": 8.18.1 - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-rc.115 ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/platform-node@4.0.0-beta.86(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(ioredis@5.9.3(supports-color@8.1.1))": + "@effect/platform-node@4.0.0-beta.86(effect@4.0.0-beta.86)(ioredis@5.9.3(supports-color@8.1.1))": dependencies: - "@effect/platform-node-shared": 4.0.0-beta.86(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4) - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + "@effect/platform-node-shared": 4.0.0-beta.86(effect@4.0.0-beta.86) + effect: 4.0.0-beta.86 ioredis: 5.9.3(supports-color@8.1.1) mime: 4.1.0 undici: 8.3.0 @@ -10273,19 +10359,19 @@ snapshots: - bufferutil - utf-8-validate - "@effect/platform-node@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(redis@6.2.1(@opentelemetry/api@1.9.1))": + "@effect/platform-node@4.0.0-rc.115(effect@4.0.0-rc.115)(redis@6.2.1(@opentelemetry/api@1.9.1))": dependencies: - "@effect/platform-node-shared": 4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4) - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + "@effect/platform-node-shared": 4.0.0-rc.115(effect@4.0.0-rc.115) + effect: 4.0.0-rc.115 redis: 6.2.1(@opentelemetry/api@1.9.1) undici: 8.10.2 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/sql-sqlite-node@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)": + "@effect/sql-sqlite-node@4.0.0-rc.115(effect@4.0.0-rc.115)": dependencies: - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-rc.115 "@effect/tsgo-darwin-arm64@0.45.0": optional: true @@ -10318,9 +10404,9 @@ snapshots: "@effect/tsgo-win32-arm64": 0.45.0 "@effect/tsgo-win32-x64": 0.45.0 - "@effect/vitest@4.0.0-rc.114(effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)))": + "@effect/vitest@4.0.0-rc.115(effect@4.0.0-rc.115)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)))": dependencies: - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 + effect: 4.0.0-rc.115 vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)) "@emnapi/core@1.10.0": @@ -10732,6 +10818,24 @@ snapshots: dependencies: sparse-bitfield: 3.0.3 + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4": + optional: true + + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4": + optional: true + + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4": + optional: true + + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4": + optional: true + + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4": + optional: true + + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4": + optional: true + "@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)": dependencies: "@emnapi/core": 1.10.0 @@ -12935,7 +13039,20 @@ snapshots: minimatch: 9.0.1 semver: 7.7.3 - effect@https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4: {} + effect@4.0.0-beta.86: + dependencies: + "@standard-schema/spec": 1.1.0 + fast-check: 4.9.0 + find-my-way-ts: 0.1.6 + ini: 7.0.0 + kubernetes-types: 1.30.0 + msgpackr: 2.1.0 + multipasta: 0.2.8 + toml: 4.3.0 + uuid: 14.0.2 + yaml: 2.9.0 + + effect@4.0.0-rc.115: {} electron-to-chromium@1.5.267: optional: true @@ -13389,6 +13506,8 @@ snapshots: filter-obj@5.1.0: {} + find-my-way-ts@0.1.6: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -13679,6 +13798,8 @@ snapshots: ini@1.3.8: {} + ini@7.0.0: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -13981,6 +14102,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kubernetes-types@1.30.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -14239,8 +14362,26 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.4: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-darwin-x64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-arm": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-arm64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-x64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-win32-x64": 3.0.4 + optional: true + + msgpackr@2.1.0: + optionalDependencies: + msgpackr-extract: 3.0.4 + muggle-string@0.4.1: {} + multipasta@0.2.8: {} + nanoid@3.3.12: {} nanoid@5.1.11: {} @@ -14261,6 +14402,11 @@ snapshots: node-addon-api@7.1.1: optional: true + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + node-releases@2.0.27: optional: true @@ -15417,6 +15563,8 @@ snapshots: token-stream@1.0.0: {} + toml@4.3.0: {} + tough-cookie@6.0.1: dependencies: tldts: 7.0.19 @@ -15612,6 +15760,8 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.20 + uuid@14.0.2: {} + validator@13.15.35: {} vfile-message@4.0.3: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 701b1417e..f6bfcf271 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,5 @@ packages: - packages/* -blockExoticSubdeps: false allowBuilds: "@parcel/watcher": false better-sqlite3: true @@ -17,15 +16,15 @@ minimumReleaseAgeExclude: - "@effect/tsgo-win32-arm64@0.45.0" - "@effect/tsgo-win32-x64@0.45.0" - "@effect/tsgo@0.45.0" - - "@effect/atom-vue@4.0.0-rc.114" - - "@effect/platform-browser@4.0.0-rc.114" - - "@effect/platform-node-shared@4.0.0-rc.114" - - "@effect/platform-node@4.0.0-rc.114" - - "@effect/sql-sqlite-node@4.0.0-rc.114" - - "@effect/vitest@4.0.0-rc.114" + - "@effect/atom-vue@4.0.0-rc.115" + - "@effect/platform-browser@4.0.0-rc.115" + - "@effect/platform-node-shared@4.0.0-rc.115" + - "@effect/platform-node@4.0.0-rc.115" + - "@effect/sql-sqlite-node@4.0.0-rc.115" + - "@effect/vitest@4.0.0-rc.115" + - effect@4.0.0-rc.115 overrides: date-fns: ^4.4.0 - effect: https://pkg.pr.new/Effect-TS/effect/effect@cdb3ac9171c63fade3e70d12d6d0de52ae164bc4 fast-check: ^4.9.0 vue: ^3.5.35 patchedDependencies: diff --git a/repos/effect/.changeset/pre/faster-schema-construction.md b/repos/effect/.changeset/pre/faster-schema-construction.md new file mode 100644 index 000000000..3310a7c53 --- /dev/null +++ b/repos/effect/.changeset/pre/faster-schema-construction.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Optimize schema initialization while preserving custom constructor options. diff --git a/repos/effect/.changeset/pre/fix-http-toweb-null-body-status.md b/repos/effect/.changeset/pre/fix-http-toweb-null-body-status.md new file mode 100644 index 000000000..dccd82337 --- /dev/null +++ b/repos/effect/.changeset/pre/fix-http-toweb-null-body-status.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +--- + +Omit response bodies for statuses 204, 205, and 304 in `HttpServerResponse.toWeb` and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw `ReadableStream` bodies, and finalize request resources without starting omitted Effect streams. diff --git a/repos/effect/.changeset/pre/fix-openapi-client-paths.md b/repos/effect/.changeset/pre/fix-openapi-client-paths.md new file mode 100644 index 000000000..04b3c50ec --- /dev/null +++ b/repos/effect/.changeset/pre/fix-openapi-client-paths.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Quote static paths and percent-encode path parameters in generated HTTP clients, including SSE and binary stream methods. Reject empty parameters and dot segments with an HTTP client error before sending a request. diff --git a/repos/effect/.changeset/pre/parameterize-persistence-lookup-keys.md b/repos/effect/.changeset/pre/parameterize-persistence-lookup-keys.md new file mode 100644 index 000000000..92960c36a --- /dev/null +++ b/repos/effect/.changeset/pre/parameterize-persistence-lookup-keys.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Parameterize persistence lookup keys in both SQL backing stores' `getMany` queries. diff --git a/repos/effect/packages/ai/anthropic/CHANGELOG.md b/repos/effect/packages/ai/anthropic/CHANGELOG.md index 976cd390c..c72a03eab 100644 --- a/repos/effect/packages/ai/anthropic/CHANGELOG.md +++ b/repos/effect/packages/ai/anthropic/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-anthropic +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/ai/anthropic/package.json b/repos/effect/packages/ai/anthropic/package.json index b198bdbab..52d8fbf01 100644 --- a/repos/effect/packages/ai/anthropic/package.json +++ b/repos/effect/packages/ai/anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-anthropic", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "An Anthropic provider integration for Effect AI SDK", diff --git a/repos/effect/packages/ai/openai-compat/CHANGELOG.md b/repos/effect/packages/ai/openai-compat/CHANGELOG.md index d90c5021e..5b012e6a8 100644 --- a/repos/effect/packages/ai/openai-compat/CHANGELOG.md +++ b/repos/effect/packages/ai/openai-compat/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openai-compat +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/ai/openai-compat/package.json b/repos/effect/packages/ai/openai-compat/package.json index 6f3702053..0910f8d81 100644 --- a/repos/effect/packages/ai/openai-compat/package.json +++ b/repos/effect/packages/ai/openai-compat/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openai-compat", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "An OpenAI compat integration for Effect", diff --git a/repos/effect/packages/ai/openai/CHANGELOG.md b/repos/effect/packages/ai/openai/CHANGELOG.md index 9cd78b267..5bfd0f85f 100644 --- a/repos/effect/packages/ai/openai/CHANGELOG.md +++ b/repos/effect/packages/ai/openai/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openai +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/ai/openai/package.json b/repos/effect/packages/ai/openai/package.json index d53d870b2..7c7554d3b 100644 --- a/repos/effect/packages/ai/openai/package.json +++ b/repos/effect/packages/ai/openai/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openai", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "An OpenAI provider integration for Effect AI SDK", diff --git a/repos/effect/packages/ai/openrouter/CHANGELOG.md b/repos/effect/packages/ai/openrouter/CHANGELOG.md index 6a89a7a4c..f7e22cb87 100644 --- a/repos/effect/packages/ai/openrouter/CHANGELOG.md +++ b/repos/effect/packages/ai/openrouter/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openrouter +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/ai/openrouter/package.json b/repos/effect/packages/ai/openrouter/package.json index 2f446d6c0..9f05c59d4 100644 --- a/repos/effect/packages/ai/openrouter/package.json +++ b/repos/effect/packages/ai/openrouter/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openrouter", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "An OpenRouter provider integration for Effect AI SDK", diff --git a/repos/effect/packages/atom/react/CHANGELOG.md b/repos/effect/packages/atom/react/CHANGELOG.md index 4827788c0..b5e964c2d 100644 --- a/repos/effect/packages/atom/react/CHANGELOG.md +++ b/repos/effect/packages/atom/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-react +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/atom/react/package.json b/repos/effect/packages/atom/react/package.json index 26edef3f7..94adc90b0 100644 --- a/repos/effect/packages/atom/react/package.json +++ b/repos/effect/packages/atom/react/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-react", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "React bindings for the Effect Atom modules", diff --git a/repos/effect/packages/atom/solid/CHANGELOG.md b/repos/effect/packages/atom/solid/CHANGELOG.md index 28eb8a81c..b8d4af9bf 100644 --- a/repos/effect/packages/atom/solid/CHANGELOG.md +++ b/repos/effect/packages/atom/solid/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-solid +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/atom/solid/package.json b/repos/effect/packages/atom/solid/package.json index b9834bb7b..ca437fb06 100644 --- a/repos/effect/packages/atom/solid/package.json +++ b/repos/effect/packages/atom/solid/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-solid", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "SolidJS bindings for the Effect Atom modules", diff --git a/repos/effect/packages/atom/vue/CHANGELOG.md b/repos/effect/packages/atom/vue/CHANGELOG.md index e7d644638..56e3cb6ee 100644 --- a/repos/effect/packages/atom/vue/CHANGELOG.md +++ b/repos/effect/packages/atom/vue/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-vue +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/atom/vue/package.json b/repos/effect/packages/atom/vue/package.json index fe1d2e891..9fa2e6765 100644 --- a/repos/effect/packages/atom/vue/package.json +++ b/repos/effect/packages/atom/vue/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-vue", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "Vue bindings for the Effect Atom modules", diff --git a/repos/effect/packages/effect/CHANGELOG.md b/repos/effect/packages/effect/CHANGELOG.md index e1585ca6d..9e7c1dcbd 100644 --- a/repos/effect/packages/effect/CHANGELOG.md +++ b/repos/effect/packages/effect/CHANGELOG.md @@ -1,5 +1,15 @@ # effect +## 4.0.0-rc.115 + +### Patch Changes + +- [#8196](https://github.com/Effect-TS/effect/pull/8196) [`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3) Thanks @gcanti! - Optimize schema initialization while preserving custom constructor options. + +- [#8190](https://github.com/Effect-TS/effect/pull/8190) [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff) Thanks @javascript-unsafe! - Omit response bodies for statuses 204, 205, and 304 in `HttpServerResponse.toWeb` and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw `ReadableStream` bodies, and finalize request resources without starting omitted Effect streams. + +- [#8187](https://github.com/Effect-TS/effect/pull/8187) [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8) Thanks @tim-smart! - Parameterize persistence lookup keys in both SQL backing stores' `getMany` queries. + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/effect/package.json b/repos/effect/packages/effect/package.json index ea3707531..96edea2fa 100644 --- a/repos/effect/packages/effect/package.json +++ b/repos/effect/packages/effect/package.json @@ -1,7 +1,7 @@ { "name": "effect", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "The missing standard library for TypeScript, for writing production-grade software.", "homepage": "https://effect.website", @@ -116,6 +116,6 @@ "immer": "^11.1.18", "protobufjs": "^8.8.0", "tinybench": "^6.1.6", - "valibot": "^1.4.2" + "valibot": "^1.5.0" } } diff --git a/repos/effect/packages/effect/runtimeperf/config.json b/repos/effect/packages/effect/runtimeperf/config.json index 01ec037fe..516e43c7c 100644 --- a/repos/effect/packages/effect/runtimeperf/config.json +++ b/repos/effect/packages/effect/runtimeperf/config.json @@ -1171,6 +1171,13 @@ "size": 2 }, "cases": [ + { + "name": "adapter-make-valid", + "export": "makeValid", + "scenario": "adapter-make-valid", + "operation": "make", + "adapter": "Sync" + }, { "name": "adapter-parser-decode-exit-invalid", "export": "parserExitInvalid", @@ -1246,6 +1253,44 @@ "size": 32 }, "cases": [ + { + "name": "schema-creation-object-2-effect", + "export": "effectSchemaCreationObject2", + "scenario": "schema-creation-object-2", + "implementation": "effect", + "operation": "schema", + "size": 2 + }, + { + "name": "schema-creation-object-32-effect", + "export": "effectSchemaCreationObject32", + "scenario": "schema-creation-object-32", + "implementation": "effect", + "operation": "schema" + }, + { + "name": "schema-creation-object-256-effect", + "export": "effectSchemaCreationObject256", + "scenario": "schema-creation-object-256", + "implementation": "effect", + "operation": "schema", + "size": 256 + }, + { + "name": "schema-creation-encoded-record-effect", + "export": "effectSchemaCreationEncodedRecord", + "scenario": "schema-creation-encoded-record", + "implementation": "effect", + "operation": "schema" + }, + { + "name": "first-make-object-2-effect", + "export": "effectFirstMakeObject2", + "scenario": "first-make-object-2", + "implementation": "effect", + "operation": "first-make", + "size": 2 + }, { "name": "schema-creation-template-literal-effect", "export": "effectSchemaCreationTemplateLiteral", @@ -1257,6 +1302,17 @@ ], "size": 5 }, + { + "name": "schema-creation-template-literal-256-effect", + "export": "effectSchemaCreationTemplateLiteral256", + "scenario": "schema-creation-template-literal-256", + "implementation": "effect", + "operation": "schema", + "astTags": [ + "TemplateLiteral" + ], + "size": 256 + }, { "name": "first-decode-checked-object-32-effect", "export": "effectFirstDecodeCheckedObject32", diff --git a/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts b/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts index 2a84cab07..e5d891cd7 100644 --- a/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts +++ b/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts @@ -11,6 +11,11 @@ const schema = Schema.Struct({ const input = { a: "a", b: 1 } const invalidInput = { a: "a", b: "invalid" } +export const makeValid = () => ({ + run: () => schema.make(input), + validate: (result) => assert.deepEqual(result, input) +}) + export const parserExitInvalid = () => { const run = SchemaParser.decodeUnknownExit(schema) return { diff --git a/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts b/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts index 2f62d5ac0..59dc535f9 100644 --- a/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts +++ b/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts @@ -14,7 +14,32 @@ const makeEffectCheckedSchema = () => const makeEffectTemplateLiteralSchema = () => Schema.TemplateLiteral(["prefix-", Schema.String, "-middle-", Schema.Number, "-suffix"]) +const templateLiteral256Parts = Array.from({ length: 256 }, (_, index) => Schema.Literal(`part${index}`)) +const makeEffectTemplateLiteral256Schema = () => Schema.TemplateLiteral(templateLiteral256Parts) + const makeEffectRecordSchema = () => Schema.Record(Schema.String, Schema.String) +const makeEffectEncodedRecordSchema = () => + Schema.Record( + Schema.String.pipe(Schema.decodeTo(Schema.Number, SchemaTransformation.passthrough())), + Schema.String + ) + +const makeEffectObject2Schema = () => + Schema.Struct({ + name: Schema.String, + age: Schema.Number + }) + +const makeObjectFields = (size: number) => + Object.fromEntries(Array.from({ length: size }, (_, index) => [`field${index}`, Schema.NonEmptyString])) + +const object32Fields = makeObjectFields(32) +const object256Fields = makeObjectFields(256) + +const makeEffectObject32Schema = () => Schema.Struct(object32Fields) +const makeEffectObject256Schema = () => Schema.Struct(object256Fields) + +const object2Input = { name: "John", age: 42 } const literalValues100 = Array.from({ length: 100 }, (_, index) => `value${index}`) @@ -53,6 +78,36 @@ export const effectSchemaCreationTemplateLiteral = () => ({ validate: (schema) => assert.equal(schema.ast._tag, "TemplateLiteral") }) +export const effectSchemaCreationTemplateLiteral256 = () => ({ + run: makeEffectTemplateLiteral256Schema, + validate: (schema) => assert.equal(schema.ast._tag, "TemplateLiteral") +}) + +export const effectSchemaCreationObject2 = () => ({ + run: makeEffectObject2Schema, + validate: (schema) => assert.equal(schema.ast._tag, "Objects") +}) + +export const effectSchemaCreationObject32 = () => ({ + run: makeEffectObject32Schema, + validate: (schema) => assert.equal(schema.ast._tag, "Objects") +}) + +export const effectSchemaCreationObject256 = () => ({ + run: makeEffectObject256Schema, + validate: (schema) => assert.equal(schema.ast._tag, "Objects") +}) + +export const effectSchemaCreationEncodedRecord = () => ({ + run: makeEffectEncodedRecordSchema, + validate: (schema) => assert.equal(schema.ast._tag, "Objects") +}) + +export const effectFirstMakeObject2 = () => ({ + run: () => makeEffectObject2Schema().make(object2Input), + validate: (result) => assert.deepEqual(result, object2Input) +}) + export const effectFirstDecodeCheckedObject32 = () => ({ run: () => SchemaParser.decodeUnknownExit(makeEffectCheckedSchema())(input), validate: (result) => assert.equal(result._tag, "Success") diff --git a/repos/effect/packages/effect/src/SchemaAST.ts b/repos/effect/packages/effect/src/SchemaAST.ts index 6f63b4b1f..0b6a455c1 100644 --- a/repos/effect/packages/effect/src/SchemaAST.ts +++ b/repos/effect/packages/effect/src/SchemaAST.ts @@ -1377,10 +1377,18 @@ type TemplateLiteralPart = | TemplateLiteral | Union -function isTemplateLiteralPart(ast: AST, path: string, validated: WeakSet): ast is TemplateLiteralPart { +function formatTemplateLiteralPath(path: string | number): string { + return typeof path === "number" ? `parts[${path}]` : path +} + +function isTemplateLiteralPart( + ast: AST, + path: string | number, + validated: Set +): ast is TemplateLiteralPart { if (validated.has(ast)) return true if (ast.encoding) { - throw new Error(`TemplateLiteral parts cannot have an encoding at ${path}`) + throw new Error(`TemplateLiteral parts cannot have an encoding at ${formatTemplateLiteralPath(path)}`) } let valid: boolean switch (ast._tag) { @@ -1392,14 +1400,18 @@ function isTemplateLiteralPart(ast: AST, path: string, validated: WeakSet): case "Literal": valid = !ast.checks break - case "TemplateLiteral": + case "TemplateLiteral": { + const currentPath = formatTemplateLiteralPath(path) valid = !ast.checks && - ast.parts.every((part, index) => isTemplateLiteralPart(part, `${path}.parts[${index}]`, validated)) + ast.parts.every((part, index) => isTemplateLiteralPart(part, `${currentPath}.parts[${index}]`, validated)) break - case "Union": + } + case "Union": { + const currentPath = formatTemplateLiteralPath(path) valid = !ast.checks && - ast.types.every((part, index) => isTemplateLiteralPart(part, `${path}.types[${index}]`, validated)) + ast.types.every((part, index) => isTemplateLiteralPart(part, `${currentPath}.types[${index}]`, validated)) break + } default: return false } @@ -1477,10 +1489,10 @@ export const TemplateLiteral: new( super(annotations, checks, encoding, context) const encodedParts: Array = [] const literals: Array = [] - const validated = new WeakSet() + const validated = new Set() for (let index = 0; index < parts.length; index++) { const part = parts[index] - if (!isTemplateLiteralPart(part, `parts[${index}]`, validated)) { + if (!isTemplateLiteralPart(part, index, validated)) { throw new Error(`Invalid TemplateLiteral part ${part._tag}`) } encodedParts.push(part) @@ -2573,8 +2585,23 @@ function isIndexSignatureParameterSide(ast: AST): ast is IndexSignatureParameter } } +function isIndexSignatureParameterEncodedSide(ast: AST): boolean { + const encoded = getLastEncoding(ast) + switch (encoded._tag) { + case "String": + case "Number": + case "Symbol": + case "TemplateLiteral": + return true + case "Union": + return encoded.types.every(isIndexSignatureParameterEncodedSide) + default: + return false + } +} + function isIndexSignatureParameter(ast: AST): ast is IndexSignatureParameter { - return isIndexSignatureParameterSide(ast) && isIndexSignatureParameterSide(toEncoded(ast)) + return isIndexSignatureParameterSide(ast) && isIndexSignatureParameterEncodedSide(ast) } /** @@ -2727,7 +2754,16 @@ export const Objects: new( this.encodingChecks = encodingChecks // Duplicate property signatures - const duplicates = propertySignatures.map((ps) => ps.name).filter((name, i, arr) => arr.indexOf(name) !== i) + const seen = new Set() + const duplicates: Array = [] + for (const propertySignature of propertySignatures) { + const name = propertySignature.name + if (seen.has(name)) { + duplicates.push(name) + } else { + seen.add(name) + } + } if (duplicates.length > 0) { throw new Error(`Duplicate identifiers: ${JSON.stringify(duplicates)}. ts(2300)`) } @@ -3780,20 +3816,6 @@ function formatIsOptional(isOptional: boolean | undefined): string { return isOptional ? "?" : "" } -/** @internal */ -export function memoizeThunk(f: () => A): () => A { - let done = false - let a: A - return () => { - if (done) { - return a - } - a = f() - done = true - return a - } -} - /** * AST node for lazy/recursive schemas. * @@ -3866,7 +3888,8 @@ export const Suspend: new( throw new Error("Cannot add checks to Suspend") } super(annotations, undefined, encoding, context) - this.thunk = memoizeThunk(thunk) + let ast: AST + this.thunk = () => ast ??= thunk() } /** @internal */ getParser(compile: SchemaParser.Compiler): SchemaParser.Parser { diff --git a/repos/effect/packages/effect/src/SchemaParser.ts b/repos/effect/packages/effect/src/SchemaParser.ts index a3fcf6411..47b068c7c 100644 --- a/repos/effect/packages/effect/src/SchemaParser.ts +++ b/repos/effect/packages/effect/src/SchemaParser.ts @@ -41,9 +41,10 @@ import * as SchemaIssue from "./SchemaIssue.ts" * @since 4.0.0 */ export function makeEffect(schema: S) { - const parser = runWithCompiler(constructorCompiler, SchemaAST.toType(schema.ast)) + const ast = schema.ast + let parser: ReturnType> return (input: S["~type.make.in"], options?: Schema.MakeOptions): Effect.Effect => { - return parser( + return (parser ??= runWithCompiler(constructorCompiler, SchemaAST.toType(ast)))( input, options?.disableChecks ? options?.parseOptions ? { ...options.parseOptions, disableChecks: true } : { disableChecks: true } diff --git a/repos/effect/packages/effect/src/internal/schema/make.ts b/repos/effect/packages/effect/src/internal/schema/make.ts index 44e4d194a..f83575a02 100644 --- a/repos/effect/packages/effect/src/internal/schema/make.ts +++ b/repos/effect/packages/effect/src/internal/schema/make.ts @@ -25,10 +25,15 @@ const SchemaProto = { /** @internal */ export function make(ast: S["ast"], options?: object): S { function Schema() {} - const self = Object.defineProperties( - Object.setPrototypeOf(Schema, SchemaProto), - Object.getOwnPropertyDescriptors({ ...options }) - ) + const self = Object.setPrototypeOf(Schema, SchemaProto) + if ( + options && + (Object.hasOwn(options, "name") || Object.hasOwn(options, "length") || Object.hasOwn(options, "__proto__")) + ) { + Object.defineProperties(self, Object.getOwnPropertyDescriptors({ ...options })) + } else { + Object.assign(self, options) + } self.ast = ast self.rebuild = (ast: SchemaAST.AST) => make(ast, options) self.makeEffect = SchemaParser.makeEffect(self) diff --git a/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts b/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts index 6fd9646bb..5032387d3 100644 --- a/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts +++ b/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts @@ -153,8 +153,8 @@ function recur(ast: SchemaAST.AST, path: ReadonlyArray): Equivalenc }) } case "Suspend": { - const get = SchemaAST.memoizeThunk(() => recur(ast.thunk(), path)) - return Equivalence.make((a, b) => get()(a, b)) + let equivalence: Equivalence.Equivalence + return Equivalence.make((a, b) => (equivalence ??= recur(ast.thunk(), path))(a, b)) } } } diff --git a/repos/effect/packages/effect/src/internal/schema/toFormatter.ts b/repos/effect/packages/effect/src/internal/schema/toFormatter.ts index 419559ada..21eef24e3 100644 --- a/repos/effect/packages/effect/src/internal/schema/toFormatter.ts +++ b/repos/effect/packages/effect/src/internal/schema/toFormatter.ts @@ -222,8 +222,8 @@ export function toFormatter(ast: SchemaAST.AST, options?: { } } case "Suspend": { - const get = SchemaAST.memoizeThunk(() => recur(ast.thunk())) - return (value) => get()(value) + let formatter: Formatter + return (value) => (formatter ??= recur(ast.thunk()))(value) } } } diff --git a/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts b/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts index 95e89f6a1..21737180d 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts @@ -295,9 +295,12 @@ export const toWebHandlerWith = { - if (request.method !== "HEAD") response = scopeTransferToStream(response) + const withoutBody = request.method === "HEAD" + if (!Response.omitsBody(response, withoutBody)) { + response = scopeTransferToStream(response) + } ;(request as any)[resolveSymbol]( - Response.toWeb(response, { withoutBody: request.method === "HEAD", context }) + Response.toWeb(response, { withoutBody, context }) ) return Effect.void }, middleware) diff --git a/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts b/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts index 9a3dd5c18..11148609a 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts @@ -14,7 +14,7 @@ import type * as ByteSize from "../../ByteSize.ts" import * as Context from "../../Context.ts" import * as Effect from "../../Effect.ts" import * as ErrorReporter from "../../ErrorReporter.ts" -import { dual } from "../../Function.ts" +import { constVoid, dual } from "../../Function.ts" import * as Inspectable from "../../Inspectable.ts" import { PipeInspectableProto } from "../../internal/core.ts" import * as Option from "../../Option.ts" @@ -965,14 +965,27 @@ export const setStatus: { }) ) +/** + * Returns whether a response must omit its body because its status is 204, 205, + * or 304, or `withoutBody` is true. + * + * @category predicates + * @since 4.0.0 + */ +export const omitsBody = (response: HttpServerResponse, withoutBody = false): boolean => + withoutBody || response.status === 204 || response.status === 205 || response.status === 304 + /** * Converts an `HttpServerResponse` to a Web `Response`. * * **Details** * * Cookies are appended as `Set-Cookie` headers. Stream bodies are converted using - * the supplied context, and `withoutBody` can be used for responses such as HEAD - * responses. + * the supplied context. + * + * Bodies are omitted for statuses 204, 205, and 304, or when `withoutBody` is true + * (for example, for HEAD responses). Omitted raw `ReadableStream` bodies are + * cancelled without awaiting completion, and cancellation errors are ignored. * * @category converting * @since 4.0.0 @@ -991,14 +1004,17 @@ export const toWeb = ( headers.append("set-cookie", header) } } - if (options?.withoutBody) { + const body = response.body + if (omitsBody(response, options?.withoutBody)) { + if (body._tag === "Raw" && isReadableStream(body.body)) { + body.body.cancel().catch(constVoid) + } return new Response(undefined, { status: response.status, statusText: response.statusText as string, headers }) } - const body = response.body switch (body._tag) { case "Empty": { return new Response(undefined, { diff --git a/repos/effect/packages/effect/src/unstable/persistence/Persistence.ts b/repos/effect/packages/effect/src/unstable/persistence/Persistence.ts index 631df6ed7..0572e1bb0 100644 --- a/repos/effect/packages/effect/src/unstable/persistence/Persistence.ts +++ b/repos/effect/packages/effect/src/unstable/persistence/Persistence.ts @@ -385,11 +385,6 @@ export const layerBackingSqlMultiTable: Layer.Layer< `.unprepared }) - const wrapString = sql.onDialectOrElse({ - mssql: () => (s: string) => `N'${s}'`, - orElse: () => (s: string) => `'${s}'` - }) - return identity({ get: (key) => sql< @@ -419,9 +414,9 @@ export const layerBackingSqlMultiTable: Layer.Layer< }) ), getMany: (keys) => - sql<{ id: string; value: string }>`SELECT id, value FROM ${table} WHERE id IN (${ - sql.literal(keys.map(wrapString).join(", ")) - }) AND (expires IS NULL OR expires > ${clock.currentTimeMillisUnsafe()})`.unprepared.pipe( + sql<{ id: string; value: string }>`SELECT id, value FROM ${table} WHERE ${ + sql.in("id", keys) + } AND (expires IS NULL OR expires > ${clock.currentTimeMillisUnsafe()})`.unprepared.pipe( Effect.mapError((cause) => new PersistenceError({ message: `Failed to getMany from backing store`, @@ -726,11 +721,6 @@ export const layerBackingSql: Layer.Layer< `.unprepared }) - const wrapString = sql.onDialectOrElse({ - mssql: () => (s: string) => `N'${s}'`, - orElse: () => (s: string) => `'${s}'` - }) - return BackingPersistence.of({ make: Effect.fnUntraced(function*(storeId) { const clock = yield* Clock.Clock @@ -764,9 +754,9 @@ export const layerBackingSql: Layer.Layer< }) ), getMany: (keys) => - sql<{ id: string; value: string }>`SELECT id, value FROM ${table} WHERE store_id = ${storeId} AND id IN (${ - sql.literal(keys.map(wrapString).join(", ")) - }) AND (expires IS NULL OR expires > ${clock.currentTimeMillisUnsafe()})`.unprepared.pipe( + sql<{ id: string; value: string }>`SELECT id, value FROM ${table} WHERE store_id = ${storeId} AND ${ + sql.in("id", keys) + } AND (expires IS NULL OR expires > ${clock.currentTimeMillisUnsafe()})`.unprepared.pipe( Effect.mapError((cause) => new PersistenceError({ message: `Failed to getMany from backing store`, diff --git a/repos/effect/packages/effect/test/schema/Schema.test.ts b/repos/effect/packages/effect/test/schema/Schema.test.ts index 60168097d..0dce68d6a 100644 --- a/repos/effect/packages/effect/test/schema/Schema.test.ts +++ b/repos/effect/packages/effect/test/schema/Schema.test.ts @@ -595,6 +595,20 @@ Missing key ) }) + it("should throw an error if a large struct has duplicate property signatures", () => { + throws( + () => + new SchemaAST.Objects( + Array.from( + { length: 32 }, + (_, index) => new SchemaAST.PropertySignature(`field${index === 31 ? 0 : index}`, Schema.String.ast) + ), + [] + ), + new Error(`Duplicate identifiers: ["field0"]. ts(2300)`) + ) + }) + describe("onExcessProperty", () => { it("error", async () => { const schema = Schema.Struct({ @@ -3507,6 +3521,19 @@ Expected a value between -2147483648 and 2147483647` strictEqual((rebuilt as any).length, 2) }) + it("assigns options", () => { + const schema = Schema.make(Schema.String.ast, { + custom: "value" + }) + + assertTrue(Schema.isSchema(schema)) + strictEqual((schema as any).custom, "value") + + const rebuilt = schema.annotate({}) + + strictEqual((rebuilt as any).custom, "value") + }) + it("should throw an error when the cause contains both a schema issue and a defect", () => { const cause = Cause.combine( Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })), diff --git a/repos/effect/packages/effect/test/schema/SchemaAST.test.ts b/repos/effect/packages/effect/test/schema/SchemaAST.test.ts index f78600f1e..714aafb57 100644 --- a/repos/effect/packages/effect/test/schema/SchemaAST.test.ts +++ b/repos/effect/packages/effect/test/schema/SchemaAST.test.ts @@ -4,6 +4,21 @@ import { describe, it } from "vitest" import { deepStrictEqual, doesNotThrow, strictEqual, throws } from "../utils/assert.ts" describe("SchemaAST", () => { + describe("Suspend", () => { + it("memoizes the thunk", () => { + let calls = 0 + const ast = new SchemaAST.Suspend(() => { + calls++ + return SchemaAST.string + }) + + strictEqual(calls, 0) + strictEqual(ast.thunk(), SchemaAST.string) + strictEqual(ast.thunk(), SchemaAST.string) + strictEqual(calls, 1) + }) + }) + it("isJson", () => { strictEqual(SchemaAST.isJson(null), true) strictEqual(SchemaAST.isJson(undefined), false) @@ -704,6 +719,25 @@ describe("SchemaAST", () => { () => new SchemaAST.IndexSignature(StringFromBoolean.ast, Schema.Number.ast), new Error("Invalid index signature parameter String") ) + throws( + () => + new SchemaAST.IndexSignature( + Schema.Union([Schema.String, StringFromBoolean]).ast, + Schema.Number.ast + ), + new Error("Invalid index signature parameter Union") + ) + + const UnionFromBoolean = Schema.Boolean.pipe( + Schema.decodeTo(Schema.Union([Schema.String, Schema.Number]), { + decode: SchemaGetter.transform((b: boolean): string | number => b ? "true" : 0), + encode: SchemaGetter.transform((_value: string | number) => true) + }) + ) + throws( + () => new SchemaAST.IndexSignature(UnionFromBoolean.ast, Schema.Number.ast), + new Error("Invalid index signature parameter Union") + ) }) }) }) diff --git a/repos/effect/packages/effect/test/schema/SchemaParser.test.ts b/repos/effect/packages/effect/test/schema/SchemaParser.test.ts index a19e051eb..f9f3e9f1c 100644 --- a/repos/effect/packages/effect/test/schema/SchemaParser.test.ts +++ b/repos/effect/packages/effect/test/schema/SchemaParser.test.ts @@ -9,6 +9,7 @@ import { Ref, Result, Schema, + type SchemaAST, SchemaGetter, SchemaIssue, SchemaParser, @@ -337,6 +338,32 @@ describe("SchemaParser", () => { })) }) + describe("makeEffect", () => { + it.effect("defers deriving the type AST until the maker is called and derives it once", () => + Effect.gen(function*() { + let encodingReads = 0 + const ast = new Proxy(Schema.String.ast, { + get(target, property, receiver) { + if (property === "encoding") { + encodingReads++ + } + return Reflect.get(target, property, receiver) + } + }) + const schema: { ast: SchemaAST.AST } = { ast } + const make = SchemaParser.makeEffect(schema as Schema.Codec) + schema.ast = Schema.Number.ast + + strictEqual(encodingReads, 0) + const effect = make("a") + assertTrue(encodingReads > 0) + strictEqual(yield* effect, "a") + const reads = encodingReads + strictEqual(yield* make("b"), "b") + strictEqual(encodingReads, reads) + })) + }) + const makeMixedCause = () => Cause.combine( Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })), diff --git a/repos/effect/packages/effect/test/unstable/http/HttpEffect.test.ts b/repos/effect/packages/effect/test/unstable/http/HttpEffect.test.ts index f923bf930..6049c3836 100644 --- a/repos/effect/packages/effect/test/unstable/http/HttpEffect.test.ts +++ b/repos/effect/packages/effect/test/unstable/http/HttpEffect.test.ts @@ -151,6 +151,32 @@ describe("HttpEffect", () => { strictEqual(finalized, true) }) + test.each([204, 205, 304])("status %i closes the request scope without starting the stream", async (status) => { + let finalized = false + let streamStarted = false + const handler = HttpEffect.toWebHandler(Effect.gen(function*() { + yield* Effect.addFinalizer(() => + Effect.sync(() => { + finalized = true + }) + ) + return HttpServerResponse.stream( + Stream.fromEffect(Effect.sync(() => { + streamStarted = true + return new Uint8Array([1]) + })), + { status } + ) + })) + + const response = await handler(new Request("http://localhost:3000/")) + + strictEqual(response.status, status) + strictEqual(response.body, null) + strictEqual(streamStarted, false) + strictEqual(finalized, true) + }) + test("stream runtime", async () => { const handler = Effect.succeed(HttpServerResponse.stream( Stream.fromEffect(TestValue).pipe(Stream.map(String), Stream.encodeText) diff --git a/repos/effect/packages/effect/test/unstable/http/HttpServerResponse.test.ts b/repos/effect/packages/effect/test/unstable/http/HttpServerResponse.test.ts index db6263313..b44082cf5 100644 --- a/repos/effect/packages/effect/test/unstable/http/HttpServerResponse.test.ts +++ b/repos/effect/packages/effect/test/unstable/http/HttpServerResponse.test.ts @@ -5,6 +5,61 @@ import { HttpBody, HttpClientRequest, HttpClientResponse, HttpServerResponse } f const TestValue = Context.Reference("test/TestValue", { defaultValue: () => 0 }) describe("HttpServerResponse", () => { + describe("toWeb", () => { + it.each([ + { status: 200, withoutBody: true }, + { status: 304, withoutBody: false } + ])( + "preserves representation headers for status $status with withoutBody=$withoutBody", + ({ status, withoutBody }) => { + const web = HttpServerResponse.toWeb(HttpServerResponse.text("body", { status }), { withoutBody }) + + assert.strictEqual(web.body, null) + assert.strictEqual(web.headers.get("content-length"), "4") + assert.strictEqual(web.headers.get("content-type"), "text/plain") + } + ) + + it.each([ + { status: 200, withoutBody: true }, + { status: 204, withoutBody: false }, + { status: 205, withoutBody: false }, + { status: 304, withoutBody: false } + ])("cancels raw streams for status $status with withoutBody=$withoutBody", async ({ status, withoutBody }) => { + let cancelled = false + const body = new ReadableStream({ + cancel() { + cancelled = true + } + }) + try { + const web = HttpServerResponse.toWeb(HttpServerResponse.raw(body, { status }), { withoutBody }) + + assert.strictEqual(web.status, status) + assert.strictEqual(web.body, null) + assert.strictEqual(await web.text(), "") + assert.strictEqual(cancelled, true) + } finally { + await body.cancel() + } + }) + + for (const status of [204, 205, 304]) { + it.each([ + { name: "text", response: HttpServerResponse.text("body", { status }) }, + { name: "uint8Array", response: HttpServerResponse.uint8Array(new Uint8Array([1]), { status }) }, + { name: "raw", response: HttpServerResponse.raw("body", { status }) }, + { name: "formData", response: HttpServerResponse.formData(new FormData(), { status }) }, + { name: "stream", response: HttpServerResponse.stream(Stream.succeed(new Uint8Array([1])), { status }) } + ])(`omits $name bodies for status ${status}`, ({ response }) => { + const web = HttpServerResponse.toWeb(response) + + assert.strictEqual(web.status, status) + assert.strictEqual(web.body, null) + }) + } + }) + it("setHeader overrides body-derived content headers", () => { const response = HttpServerResponse.text("body").pipe( HttpServerResponse.setHeader("content-type", "text/custom"), diff --git a/repos/effect/packages/opentelemetry/CHANGELOG.md b/repos/effect/packages/opentelemetry/CHANGELOG.md index 6c14d53e1..dcd887b8a 100644 --- a/repos/effect/packages/opentelemetry/CHANGELOG.md +++ b/repos/effect/packages/opentelemetry/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/opentelemetry +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/opentelemetry/package.json b/repos/effect/packages/opentelemetry/package.json index a4bc89617..37ade075a 100644 --- a/repos/effect/packages/opentelemetry/package.json +++ b/repos/effect/packages/opentelemetry/package.json @@ -1,7 +1,7 @@ { "name": "@effect/opentelemetry", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "OpenTelemetry integration for Effect", "homepage": "https://effect.website", diff --git a/repos/effect/packages/platform/browser/CHANGELOG.md b/repos/effect/packages/platform/browser/CHANGELOG.md index 786d1320b..ec2f5e004 100644 --- a/repos/effect/packages/platform/browser/CHANGELOG.md +++ b/repos/effect/packages/platform/browser/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/platform-browser +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/platform/browser/package.json b/repos/effect/packages/platform/browser/package.json index 48152e67c..343d760ff 100644 --- a/repos/effect/packages/platform/browser/package.json +++ b/repos/effect/packages/platform/browser/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-browser", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Platform specific implementations for the browser", "homepage": "https://effect.website", diff --git a/repos/effect/packages/platform/bun/CHANGELOG.md b/repos/effect/packages/platform/bun/CHANGELOG.md index 77ca4b86b..49abf2d20 100644 --- a/repos/effect/packages/platform/bun/CHANGELOG.md +++ b/repos/effect/packages/platform/bun/CHANGELOG.md @@ -1,5 +1,14 @@ # @effect/platform-bun +## 4.0.0-rc.115 + +### Patch Changes + +- [#8190](https://github.com/Effect-TS/effect/pull/8190) [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff) Thanks @javascript-unsafe! - Omit response bodies for statuses 204, 205, and 304 in `HttpServerResponse.toWeb` and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw `ReadableStream` bodies, and finalize request resources without starting omitted Effect streams. +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node-shared@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/platform/bun/package.json b/repos/effect/packages/platform/bun/package.json index 9d25c3948..a4c6c024f 100644 --- a/repos/effect/packages/platform/bun/package.json +++ b/repos/effect/packages/platform/bun/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-bun", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Platform specific implementations for the Bun runtime", "homepage": "https://effect.website", diff --git a/repos/effect/packages/platform/bun/src/BunHttpServer.ts b/repos/effect/packages/platform/bun/src/BunHttpServer.ts index d716343b4..e0cfbb47a 100644 --- a/repos/effect/packages/platform/bun/src/BunHttpServer.ts +++ b/repos/effect/packages/platform/bun/src/BunHttpServer.ts @@ -46,7 +46,7 @@ import type { HttpPlatform } from "effect/unstable/http/HttpPlatform" import * as Server from "effect/unstable/http/HttpServer" import * as Error from "effect/unstable/http/HttpServerError" import * as ServerRequest from "effect/unstable/http/HttpServerRequest" -import type * as ServerResponse from "effect/unstable/http/HttpServerResponse" +import * as ServerResponse from "effect/unstable/http/HttpServerResponse" import type * as Multipart from "effect/unstable/http/Multipart" import * as UrlParams from "effect/unstable/http/UrlParams" import * as NetAddress from "effect/unstable/net/NetAddress" @@ -232,6 +232,9 @@ const makeResponse = ( context: Context.Context, scope: Scope.Scope ): Response => { + if (ServerResponse.omitsBody(response, request.method === "HEAD")) { + return ServerResponse.toWeb(response, { withoutBody: true }) + } const fields: { headers: globalThis.Headers status?: number @@ -251,9 +254,6 @@ const makeResponse = ( fields.statusText = response.statusText } - if (request.method === "HEAD") { - return new Response(undefined, fields) - } response = HttpEffect.scopeTransferToStream(response) const body = response.body switch (body._tag) { diff --git a/repos/effect/packages/platform/bun/test/BunHttpServer.test.ts b/repos/effect/packages/platform/bun/test/BunHttpServer.test.ts index ce461bfa2..eb7072f3c 100644 --- a/repos/effect/packages/platform/bun/test/BunHttpServer.test.ts +++ b/repos/effect/packages/platform/bun/test/BunHttpServer.test.ts @@ -5,7 +5,9 @@ import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as Fiber from "effect/Fiber" import * as Scope from "effect/Scope" +import * as Stream from "effect/Stream" import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient" +import * as HttpClient from "effect/unstable/http/HttpClient" import * as HttpServer from "effect/unstable/http/HttpServer" import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest" import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" @@ -113,6 +115,81 @@ const makeWebSocketServer = Effect.fnUntraced(function*(payload: string, compres }) describe("BunHttpServer", () => { + describe("body omission", () => { + for (const status of [204, 205, 304]) { + for (const bodyKind of ["text", "stream"]) { + it.live(`omits ${bodyKind} bodies for status ${status}`, () => + Effect.gen(function*() { + let finalized = false + let streamStarted = false + yield* HttpServer.serveEffect(Effect.gen(function*() { + yield* Effect.addFinalizer(() => + Effect.sync(() => { + finalized = true + }) + ) + const options = { status, contentType: "text/plain", contentLength: 4 } + return bodyKind === "text" + ? HttpServerResponse.text("body", options) + : HttpServerResponse.stream( + Stream.fromEffect(Effect.sync(() => { + streamStarted = true + return new TextEncoder().encode("body") + })), + options + ) + })) + const response = yield* HttpClient.get("/") + assert.strictEqual(response.status, status) + assert.strictEqual(yield* response.text, "") + assert.strictEqual(streamStarted, false) + assert.strictEqual(finalized, true) + if (status === 304) { + assert.strictEqual(response.headers["content-type"], "text/plain") + } + }).pipe( + Effect.timeout("2 seconds"), + Effect.provide(BunHttpServer.layerTest) + ), 5000) + } + } + + for (const [method, status] of [["HEAD", 200], ["GET", 204], ["GET", 205], ["GET", 304]] as const) { + it.live(`cancels raw streams for ${method} status ${status}`, () => + Effect.gen(function*() { + let cancelled = false + const body = new ReadableStream({ + pull(controller) { + controller.enqueue(new TextEncoder().encode("body")) + controller.close() + }, + cancel() { + cancelled = true + } + }, { highWaterMark: 0 }) + yield* Effect.addFinalizer(() => Effect.ignoreCause(Effect.promise(() => body.cancel()))) + yield* HttpServer.serveEffect(Effect.succeed(HttpServerResponse.raw(body, { + status, + contentType: "text/plain", + contentLength: 4 + }))) + const response = yield* (method === "HEAD" ? HttpClient.head("/") : HttpClient.get("/")) + assert.strictEqual(response.status, status) + assert.strictEqual(yield* response.text, "") + assert.strictEqual(cancelled, true) + if (method === "HEAD") { + assert.strictEqual(response.headers["content-length"], "4") + } + if (method === "HEAD" || status === 304) { + assert.strictEqual(response.headers["content-type"], "text/plain") + } + }).pipe( + Effect.timeout("2 seconds"), + Effect.provide(BunHttpServer.layerTest) + ), 5000) + } + }) + for ( const [name, options, expectedTag, expectedIp] of [ ["omitted hostname", { port: 0 }, "InetAddressV6", "::"], diff --git a/repos/effect/packages/platform/deno/CHANGELOG.md b/repos/effect/packages/platform/deno/CHANGELOG.md index a5f8c6556..eafd5d987 100644 --- a/repos/effect/packages/platform/deno/CHANGELOG.md +++ b/repos/effect/packages/platform/deno/CHANGELOG.md @@ -1,5 +1,14 @@ # @effect/platform-deno +## 4.0.0-rc.115 + +### Patch Changes + +- [#8190](https://github.com/Effect-TS/effect/pull/8190) [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff) Thanks @javascript-unsafe! - Omit response bodies for statuses 204, 205, and 304 in `HttpServerResponse.toWeb` and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw `ReadableStream` bodies, and finalize request resources without starting omitted Effect streams. +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node-shared@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/platform/deno/package.json b/repos/effect/packages/platform/deno/package.json index 7232bd412..acc5a0106 100644 --- a/repos/effect/packages/platform/deno/package.json +++ b/repos/effect/packages/platform/deno/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-deno", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Platform specific implementations for the Deno runtime", "homepage": "https://effect.website", diff --git a/repos/effect/packages/platform/deno/src/DenoHttpServer.ts b/repos/effect/packages/platform/deno/src/DenoHttpServer.ts index a866a8cf6..87336cfec 100644 --- a/repos/effect/packages/platform/deno/src/DenoHttpServer.ts +++ b/repos/effect/packages/platform/deno/src/DenoHttpServer.ts @@ -32,7 +32,7 @@ import type { HttpPlatform } from "effect/unstable/http/HttpPlatform" import * as Server from "effect/unstable/http/HttpServer" import * as Error from "effect/unstable/http/HttpServerError" import * as ServerRequest from "effect/unstable/http/HttpServerRequest" -import type * as ServerResponse from "effect/unstable/http/HttpServerResponse" +import * as ServerResponse from "effect/unstable/http/HttpServerResponse" import type * as Multipart from "effect/unstable/http/Multipart" import * as UrlParams from "effect/unstable/http/UrlParams" import * as NetAddress from "effect/unstable/net/NetAddress" @@ -171,7 +171,7 @@ const makeResponse = Effect.fnUntraced(function*( } if (response.statusText !== undefined) fields.statusText = response.statusText - if (request.method === "HEAD") { + if (ServerResponse.omitsBody(response, request.method === "HEAD")) { yield* cancelResponseBody(response.body) return new Response(undefined, fields) } @@ -552,8 +552,8 @@ const bufferedWebSocket = (ws: WebSocket): Socket.WebSocketLike => { } const cancelResponseBody = (body: HttpBody.HttpBody): Effect.Effect => { - const stream = (body as any).body - if ((body._tag === "Raw" || body._tag === "Uint8Array") && stream instanceof ReadableStream) { + if (body._tag === "Raw" && typeof ReadableStream !== "undefined" && body.body instanceof ReadableStream) { + const stream = body.body return Effect.ignoreCause(Effect.promise(() => stream.cancel())) } return Effect.void diff --git a/repos/effect/packages/platform/deno/test/DenoHttpServer.test.ts b/repos/effect/packages/platform/deno/test/DenoHttpServer.test.ts index 11d624e3c..9a17b8121 100644 --- a/repos/effect/packages/platform/deno/test/DenoHttpServer.test.ts +++ b/repos/effect/packages/platform/deno/test/DenoHttpServer.test.ts @@ -42,6 +42,81 @@ const todoResponse = HttpServerResponse.schemaJson(Todo) const fixture = `${import.meta.dirname}/fixtures/text.txt` describe("DenoHttpServer", () => { + describe("body omission", () => { + for (const status of [204, 205, 304]) { + for (const bodyKind of ["text", "stream"]) { + it.live(`omits ${bodyKind} bodies for status ${status}`, () => + Effect.gen(function*() { + let finalized = false + let streamStarted = false + yield* HttpServer.serveEffect(Effect.gen(function*() { + yield* Effect.addFinalizer(() => + Effect.sync(() => { + finalized = true + }) + ) + const options = { status, contentType: "text/plain", contentLength: 4 } + return bodyKind === "text" + ? HttpServerResponse.text("body", options) + : HttpServerResponse.stream( + Stream.fromEffect(Effect.sync(() => { + streamStarted = true + return new TextEncoder().encode("body") + })), + options + ) + })) + const response = yield* HttpClient.get("/") + assert.strictEqual(response.status, status) + assert.strictEqual(yield* response.text, "") + assert.strictEqual(streamStarted, false) + assert.strictEqual(finalized, true) + if (status === 304) { + assert.strictEqual(response.headers["content-type"], "text/plain") + } + }).pipe( + Effect.timeout("2 seconds"), + Effect.provide(DenoHttpServer.layerTest) + ), 5000) + } + } + + for (const [method, status] of [["HEAD", 200], ["GET", 204], ["GET", 205], ["GET", 304]] as const) { + it.live(`cancels raw streams for ${method} status ${status}`, () => + Effect.gen(function*() { + let cancelled = false + const body = new ReadableStream({ + pull(controller) { + controller.enqueue(new TextEncoder().encode("body")) + controller.close() + }, + cancel() { + cancelled = true + } + }, { highWaterMark: 0 }) + yield* Effect.addFinalizer(() => Effect.ignoreCause(Effect.promise(() => body.cancel()))) + yield* HttpServer.serveEffect(Effect.succeed(HttpServerResponse.raw(body, { + status, + contentType: "text/plain", + contentLength: 4 + }))) + const response = yield* (method === "HEAD" ? HttpClient.head("/") : HttpClient.get("/")) + assert.strictEqual(response.status, status) + assert.strictEqual(yield* response.text, "") + assert.strictEqual(cancelled, true) + if (method === "HEAD") { + assert.strictEqual(response.headers["content-length"], "4") + } + if (method === "HEAD" || status === 304) { + assert.strictEqual(response.headers["content-type"], "text/plain") + } + }).pipe( + Effect.timeout("2 seconds"), + Effect.provide(DenoHttpServer.layerTest) + ), 5000) + } + }) + it.effect("schema", () => Effect.gen(function*() { yield* HttpRouter.add( diff --git a/repos/effect/packages/platform/node-shared/CHANGELOG.md b/repos/effect/packages/platform/node-shared/CHANGELOG.md index 5afe275a3..00147d444 100644 --- a/repos/effect/packages/platform/node-shared/CHANGELOG.md +++ b/repos/effect/packages/platform/node-shared/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/platform-node-shared +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/platform/node-shared/package.json b/repos/effect/packages/platform/node-shared/package.json index e02bf4af3..ad7644f17 100644 --- a/repos/effect/packages/platform/node-shared/package.json +++ b/repos/effect/packages/platform/node-shared/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-node-shared", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Unified interfaces for common platform-specific services", "homepage": "https://effect.website", diff --git a/repos/effect/packages/platform/node/CHANGELOG.md b/repos/effect/packages/platform/node/CHANGELOG.md index 8dc49eb46..0405daefc 100644 --- a/repos/effect/packages/platform/node/CHANGELOG.md +++ b/repos/effect/packages/platform/node/CHANGELOG.md @@ -1,5 +1,13 @@ # @effect/platform-node +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node-shared@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/platform/node/package.json b/repos/effect/packages/platform/node/package.json index 10c6fced3..395ad5b22 100644 --- a/repos/effect/packages/platform/node/package.json +++ b/repos/effect/packages/platform/node/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-node", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Platform specific implementations for the Node.js runtime", "homepage": "https://effect.website", diff --git a/repos/effect/packages/sql/clickhouse/CHANGELOG.md b/repos/effect/packages/sql/clickhouse/CHANGELOG.md index f93d1e42e..cf8f61a86 100644 --- a/repos/effect/packages/sql/clickhouse/CHANGELOG.md +++ b/repos/effect/packages/sql/clickhouse/CHANGELOG.md @@ -1,5 +1,13 @@ # @effect/sql-clickhouse +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/clickhouse/package.json b/repos/effect/packages/sql/clickhouse/package.json index 7967c13c2..c86d97a5b 100644 --- a/repos/effect/packages/sql/clickhouse/package.json +++ b/repos/effect/packages/sql/clickhouse/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-clickhouse", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A Clickhouse toolkit for Effect", diff --git a/repos/effect/packages/sql/d1/CHANGELOG.md b/repos/effect/packages/sql/d1/CHANGELOG.md index d76918723..140a548fb 100644 --- a/repos/effect/packages/sql/d1/CHANGELOG.md +++ b/repos/effect/packages/sql/d1/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-d1 +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/d1/package.json b/repos/effect/packages/sql/d1/package.json index ea140bdc2..217ddc3c7 100644 --- a/repos/effect/packages/sql/d1/package.json +++ b/repos/effect/packages/sql/d1/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-d1", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A Cloudflare D1 integration for Effect", diff --git a/repos/effect/packages/sql/libsql/CHANGELOG.md b/repos/effect/packages/sql/libsql/CHANGELOG.md index 5f7aa2533..165b4e6f6 100644 --- a/repos/effect/packages/sql/libsql/CHANGELOG.md +++ b/repos/effect/packages/sql/libsql/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-libsql +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/libsql/package.json b/repos/effect/packages/sql/libsql/package.json index dd2097c40..402be64c1 100644 --- a/repos/effect/packages/sql/libsql/package.json +++ b/repos/effect/packages/sql/libsql/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-libsql", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A libSQL toolkit for Effect", diff --git a/repos/effect/packages/sql/mssql/CHANGELOG.md b/repos/effect/packages/sql/mssql/CHANGELOG.md index e991425e7..87f9a7c79 100644 --- a/repos/effect/packages/sql/mssql/CHANGELOG.md +++ b/repos/effect/packages/sql/mssql/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-mssql +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/mssql/package.json b/repos/effect/packages/sql/mssql/package.json index ab70df382..4d580428c 100644 --- a/repos/effect/packages/sql/mssql/package.json +++ b/repos/effect/packages/sql/mssql/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-mssql", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A Microsoft SQL Server toolkit for Effect", diff --git a/repos/effect/packages/sql/mysql2/CHANGELOG.md b/repos/effect/packages/sql/mysql2/CHANGELOG.md index e08c0c9bc..50172b7c7 100644 --- a/repos/effect/packages/sql/mysql2/CHANGELOG.md +++ b/repos/effect/packages/sql/mysql2/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-mysql2 +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/mysql2/package.json b/repos/effect/packages/sql/mysql2/package.json index 4b5ac0655..210dbabe2 100644 --- a/repos/effect/packages/sql/mysql2/package.json +++ b/repos/effect/packages/sql/mysql2/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-mysql2", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A MySQL toolkit for Effect", diff --git a/repos/effect/packages/sql/pg/CHANGELOG.md b/repos/effect/packages/sql/pg/CHANGELOG.md index 76115cea1..38b182b86 100644 --- a/repos/effect/packages/sql/pg/CHANGELOG.md +++ b/repos/effect/packages/sql/pg/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-pg +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/pg/package.json b/repos/effect/packages/sql/pg/package.json index 02875e1b0..c2da7d82d 100644 --- a/repos/effect/packages/sql/pg/package.json +++ b/repos/effect/packages/sql/pg/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-pg", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A PostgreSQL toolkit for Effect", diff --git a/repos/effect/packages/sql/pglite/CHANGELOG.md b/repos/effect/packages/sql/pglite/CHANGELOG.md index c3a28f981..acb06d403 100644 --- a/repos/effect/packages/sql/pglite/CHANGELOG.md +++ b/repos/effect/packages/sql/pglite/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-pglite +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/pglite/package.json b/repos/effect/packages/sql/pglite/package.json index ca36f2ee2..8a4bb4806 100644 --- a/repos/effect/packages/sql/pglite/package.json +++ b/repos/effect/packages/sql/pglite/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-pglite", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A PGlite toolkit for Effect", diff --git a/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md b/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md index a756d5199..a819560c2 100644 --- a/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-bun +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-bun/package.json b/repos/effect/packages/sql/sqlite-bun/package.json index a7b7c1367..a5fed853c 100644 --- a/repos/effect/packages/sql/sqlite-bun/package.json +++ b/repos/effect/packages/sql/sqlite-bun/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-bun", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/repos/effect/packages/sql/sqlite-do/CHANGELOG.md b/repos/effect/packages/sql/sqlite-do/CHANGELOG.md index 0f644b430..141b58f00 100644 --- a/repos/effect/packages/sql/sqlite-do/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-do/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-do +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-do/package.json b/repos/effect/packages/sql/sqlite-do/package.json index bb650a232..006ebd09a 100644 --- a/repos/effect/packages/sql/sqlite-do/package.json +++ b/repos/effect/packages/sql/sqlite-do/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-do", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/repos/effect/packages/sql/sqlite-node/CHANGELOG.md b/repos/effect/packages/sql/sqlite-node/CHANGELOG.md index 4f76b7dcc..b626c51e4 100644 --- a/repos/effect/packages/sql/sqlite-node/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-node +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-node/package.json b/repos/effect/packages/sql/sqlite-node/package.json index b497c341b..52ea06173 100644 --- a/repos/effect/packages/sql/sqlite-node/package.json +++ b/repos/effect/packages/sql/sqlite-node/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-node", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts b/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts index 76b5ab3c9..f0667478b 100644 --- a/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts @@ -23,7 +23,18 @@ const ClientLayer = Effect.gen(function*() { const testLayer = (layer: Layer.Layer) => layer.pipe(Layer.provideMerge(ClientLayer)) -const suite = (name: string, layer: Layer.Layer) => +const suite = ( + name: string, + layer: Layer.Layer, + options: { + /** + * A crafted `getMany` key which, if request keys are interpolated into SQL + * without parameterization, escapes the `IN` list and + * reads the `sqli_victim` store's entries from the `sqli_attacker` store. + */ + readonly injectionProbe: string + } +) => it.layer(testLayer(layer))(`Persistence (${name})`, (it) => { it.effect("set + get", () => Effect.gen(function*() { @@ -114,10 +125,49 @@ const suite = (name: string, layer: Layer.Layer + Effect.gen(function*() { + const persistence = yield* Persistence.BackingPersistence + const store = yield* persistence.make("test_store_quotes") + yield* store.set("it's-a-key", { name: "Alice" }, undefined) + + // Keys must be bound as parameters, not + // interpolated into the `IN` list as raw string literals. + const values = yield* store.getMany(["it's-a-key", "missing'key"]) + expect(values).toEqual([{ name: "Alice" }, undefined]) + })) + + it.effect("getMany preserves store isolation for crafted keys", () => + Effect.gen(function*() { + const persistence = yield* Persistence.BackingPersistence + const victim = yield* persistence.make("sqli_victim") + yield* victim.set("secret", { secret: "victim-data" }, undefined) + + const attacker = yield* persistence.make("sqli_attacker") + yield* attacker.set("innocent", { name: "Mallory" }, undefined) + + // The probe is a no-op key when keys are parameterized. If keys are + // interpolated into raw SQL it escapes the `IN` list and exfiltrates + // the victim store's `secret` entry into the attacker's results. + const values = yield* attacker.getMany(["secret", options.injectionProbe]) + expect(values).toEqual([undefined, undefined]) + expect(yield* attacker.get("secret")).toEqual(undefined) + })) }) -suite("table-per-store", Persistence.layerBackingSqlMultiTable) -suite("single-table", Persistence.layerBackingSql) +suite("table-per-store", Persistence.layerBackingSqlMultiTable, { + // Expands to `... WHERE id IN ('secret', '') UNION SELECT id, value FROM + // effect_persistence_sqli_victim WHERE ('1'='1') AND (expires IS NULL OR + // expires > ?)`, appending every victim row to the attacker's result set. + injectionProbe: `') UNION SELECT id, value FROM effect_persistence_sqli_victim WHERE ('1'='1` +}) +suite("single-table", Persistence.layerBackingSql, { + // Expands to `... WHERE store_id = 'sqli_attacker' AND id IN ('secret', '') + // OR store_id='sqli_victim' AND ('1'='1') AND (expires IS NULL OR expires > + // ?)`, bypassing the store isolation filter. + injectionProbe: `') OR store_id='sqli_victim' AND ('1'='1` +}) PersistedQueueTest.suite( "sql-sqlite-node", diff --git a/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md b/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md index 2dae34404..51c4f744a 100644 --- a/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-react-native +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-react-native/package.json b/repos/effect/packages/sql/sqlite-react-native/package.json index 9f6f4a5bb..074902630 100644 --- a/repos/effect/packages/sql/sqlite-react-native/package.json +++ b/repos/effect/packages/sql/sqlite-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-react-native", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md b/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md index a87343083..473ed2303 100644 --- a/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-wasm +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-wasm/package.json b/repos/effect/packages/sql/sqlite-wasm/package.json index 82c31b57c..2e8520cc1 100644 --- a/repos/effect/packages/sql/sqlite-wasm/package.json +++ b/repos/effect/packages/sql/sqlite-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-wasm", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/repos/effect/packages/tools/docgen/CHANGELOG.md b/repos/effect/packages/tools/docgen/CHANGELOG.md index 69a10e574..601e32d37 100644 --- a/repos/effect/packages/tools/docgen/CHANGELOG.md +++ b/repos/effect/packages/tools/docgen/CHANGELOG.md @@ -1,5 +1,13 @@ # @effect/docgen +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/tools/docgen/package.json b/repos/effect/packages/tools/docgen/package.json index 442aa0ba5..bf86acdc2 100644 --- a/repos/effect/packages/tools/docgen/package.json +++ b/repos/effect/packages/tools/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@effect/docgen", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "An opinionated documentation generator for Effect projects", diff --git a/repos/effect/packages/tools/doctest/CHANGELOG.md b/repos/effect/packages/tools/doctest/CHANGELOG.md index 8a6a846f9..0c2176b4e 100644 --- a/repos/effect/packages/tools/doctest/CHANGELOG.md +++ b/repos/effect/packages/tools/doctest/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/doctest +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/tools/doctest/package.json b/repos/effect/packages/tools/doctest/package.json index ae532db55..498d75e7a 100644 --- a/repos/effect/packages/tools/doctest/package.json +++ b/repos/effect/packages/tools/doctest/package.json @@ -1,6 +1,6 @@ { "name": "@effect/doctest", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "Runs TypeScript documentation examples as Vitest tests", diff --git a/repos/effect/packages/tools/openapi-generator/CHANGELOG.md b/repos/effect/packages/tools/openapi-generator/CHANGELOG.md index 1dfcd0f9d..cd16ece8f 100644 --- a/repos/effect/packages/tools/openapi-generator/CHANGELOG.md +++ b/repos/effect/packages/tools/openapi-generator/CHANGELOG.md @@ -1,5 +1,14 @@ # @effect/openapi-generator +## 4.0.0-rc.115 + +### Patch Changes + +- [#8188](https://github.com/Effect-TS/effect/pull/8188) [`ccad0fe`](https://github.com/Effect-TS/effect/commit/ccad0fe130ce46b9fa57046968d6c6245d4b4341) Thanks @tim-smart! - Quote static paths and percent-encode path parameters in generated HTTP clients, including SSE and binary stream methods. Reject empty parameters and dot segments with an HTTP client error before sending a request. +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + - @effect/platform-node@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/tools/openapi-generator/package.json b/repos/effect/packages/tools/openapi-generator/package.json index 65f813cc5..61ee8087c 100644 --- a/repos/effect/packages/tools/openapi-generator/package.json +++ b/repos/effect/packages/tools/openapi-generator/package.json @@ -1,7 +1,7 @@ { "name": "@effect/openapi-generator", "type": "module", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "license": "MIT", "description": "Generate Effect Schema types, HTTP clients, and HttpApi modules from OpenAPI specifications", "homepage": "https://effect.website", diff --git a/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts b/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts index 167b9ea7c..116a3fc55 100644 --- a/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts +++ b/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts @@ -1310,11 +1310,16 @@ const processPath = (path: string): { readonly pathTemplate: string } => { const pathIds: Array = [] - path = path.replace(/{([^}]+)}/g, (_, name) => { - const id = Utils.camelize(name) + const fragments: Array = [] + let offset = 0 + for (const match of path.matchAll(/{([^}]+)}/g)) { + fragments.push(JSON.stringify(path.slice(offset, match.index))) + const id = Utils.camelize(match[1]) pathIds.push(id) - return "${" + id + "}" - }) - const pathTemplate = "`" + path + "`" + fragments.push(`__encodePathParam(${id})`) + offset = match.index + match[0].length + } + fragments.push(JSON.stringify(path.slice(offset))) + const pathTemplate = fragments.join(" + ") return { pathIds, pathTemplate } as const } diff --git a/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts b/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts index 445b58238..01139a772 100644 --- a/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts +++ b/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts @@ -85,6 +85,20 @@ const normalizeSuccessStatus = ( return successCount === 1 && status.startsWith("2") ? "2xx" : status } +const requestToImpl = (operation: ParsedOperation, pipeline: Array, streaming = false): string => { + const request = `HttpClientRequest.${operation.method}` + const pipe = `.pipe(\n ${pipeline.join(",\n ")}\n )` + if (operation.pathIds.length === 0) { + return `${request}(${operation.pathTemplate})${pipe}` + } + const effect = `__makePathRequest(${request}, [${ + operation.pathIds.join(", ") + }], () => ${operation.pathTemplate}).pipe( + Effect.${streaming ? "map" : "flatMap"}((request) => request${pipe}) + )` + return streaming ? `Stream.unwrap(${effect})` : effect +} + /** * Create the transformer used for schema-backed HttpClient output. * @@ -261,6 +275,9 @@ ${clientErrorSource(name)}` } const helpers: Array = [commonSource] + if (operations.some((operation) => operation.pathIds.length > 0)) { + helpers.push(pathRequestSource) + } if (requiresStreaming(requirements)) { helpers.push(executeStreamRequestSource) } @@ -382,8 +399,7 @@ export const make = ( return ( `"${operation.id}": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline) ) } @@ -426,8 +442,7 @@ export const make = ( return ( `"${operation.id}Sse": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline, true) ) } @@ -469,8 +484,7 @@ export const make = ( return ( `"${operation.id}Stream": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline, true) ) } @@ -693,6 +707,9 @@ ${clientErrorSource(name)}` } const helpers: Array = [commonSource] + if (operations.some((operation) => operation.pathIds.length > 0)) { + helpers.push(pathRequestSource) + } if (requiresStreaming(requirements)) { helpers.push(executeStreamRequestSource) } @@ -819,8 +836,7 @@ export const make = ( return ( `"${operation.id}": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline) ) } @@ -862,8 +878,7 @@ export const make = ( return ( `"${operation.id}Sse": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline, true) ) } @@ -905,8 +920,7 @@ export const make = ( return ( `"${operation.id}Stream": (${params}) => ` + - `HttpClientRequest.${operation.method}(${operation.pathTemplate})` + - `.pipe(\n ${pipeline.join(",\n ")}\n )` + requestToImpl(operation, pipeline, true) ) } @@ -955,6 +969,36 @@ export const layerTransformerTs = Layer.sync( makeTransformerTs ) +const pathRequestSource = `const __encodePathParam = encodeURIComponent + const __makePathRequest = ( + method: (url: string) => HttpClientRequest.HttpClientRequest, + parameters: ReadonlyArray, + getPath: () => string, + ) => Effect.suspend(() => { + const fail = (description: string, cause?: unknown) => Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.InvalidUrlError({ + request: method(""), + cause, + description, + }), + }), + ) + if (parameters.some((value) => value === "" || /^(?:\\.|%2e){1,2}$/i.test(value))) { + return fail("Path parameters must be non-empty and cannot be dot segments") + } + let path: string + try { + path = getPath() + } catch (cause) { + return fail("Failed to encode path parameter", cause) + } + if (path.split("/").some((segment) => /^(?:\\.|%2e){1,2}$/i.test(segment))) { + return fail("Request paths cannot contain dot segments") + } + return Effect.succeed(method(path)) + })` + const commonSource = `const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) => Effect.flatMap( Effect.orElseSucceed(response.json, () => "Unexpected status code"), diff --git a/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts b/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts index 1e581af5e..caee74354 100644 --- a/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts +++ b/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts @@ -1,7 +1,7 @@ import * as OpenApiGenerator from "@effect/openapi-generator/OpenApiGenerator" import { assert, describe, it } from "@effect/vitest" import * as Effect from "effect/Effect" -import type { OpenAPISpec } from "effect/unstable/httpapi/OpenApi" +import type { OpenAPISpec, OpenAPISpecOperation } from "effect/unstable/httpapi/OpenApi" import { spawnSync } from "node:child_process" import { mkdtempSync, rmSync, writeFileSync } from "node:fs" import { dirname, join } from "node:path" @@ -739,6 +739,35 @@ export const make = ( ) : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) } + const __encodePathParam = encodeURIComponent + const __makePathRequest = ( + method: (url: string) => HttpClientRequest.HttpClientRequest, + parameters: ReadonlyArray, + getPath: () => string, + ) => Effect.suspend(() => { + const fail = (description: string, cause?: unknown) => Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.InvalidUrlError({ + request: method(""), + cause, + description, + }), + }), + ) + if (parameters.some((value) => value === "" || /^(?:\\.|%2e){1,2}$/i.test(value))) { + return fail("Path parameters must be non-empty and cannot be dot segments") + } + let path: string + try { + path = getPath() + } catch (cause) { + return fail("Failed to encode path parameter", cause) + } + if (path.split("/").some((segment) => /^(?:\\.|%2e){1,2}$/i.test(segment))) { + return fail("Request paths cannot contain dot segments") + } + return Effect.succeed(method(path)) + }) const decodeSuccess = (schema: Schema) => (response: HttpClientResponse.HttpClientResponse) => @@ -752,11 +781,13 @@ export const make = ( ) return { httpClient, - "getUser": (id, options) => HttpClientRequest.get(\`/users/\${id}\`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ + "getUser": (id, options) => __makePathRequest(HttpClientRequest.get, [id], () => "/users/" + __encodePathParam(id) + "").pipe( + Effect.flatMap((request) => request.pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ "2xx": decodeSuccess(GetUser200), orElse: unexpectedStatus })) + )) ) } } @@ -896,7 +927,7 @@ export const TestClientError = ( [ `import * as Sse from "effect/unstable/encoding/Sse"`, `readonly "streamEventsSse": () => Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof StreamEvents200Sse.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`, - `"streamEventsSse": () => HttpClientRequest.get(\`/events\`).pipe(`, + `"streamEventsSse": () => HttpClientRequest.get("/events").pipe(`, `sseRequest(StreamEvents200Sse)`, `schema: Schema.ConstraintDecoder` ] @@ -1040,8 +1071,8 @@ export const TestClientError = ( `readonly "downloadArchive": (options: { readonly config?: Config | undefined } | undefined) => Effect.Effect`, `readonly "downloadArchiveStream": () => Stream.Stream`, `readonly "downloadAvatarStream": () => Stream.Stream`, - `"downloadAvatar": (options) => HttpClientRequest.get(\`/avatar\`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ + `"downloadAvatar": (options) => HttpClientRequest.get("/avatar").pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ "2xx": decodeBinary`, `readonly "downloadCustomBinaryStream": () => Stream.Stream`, `"400": decodeError("DownloadMixedContent400", DownloadMixedContent400)`, @@ -1190,6 +1221,35 @@ export const make = ( ) : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) } + const __encodePathParam = encodeURIComponent + const __makePathRequest = ( + method: (url: string) => HttpClientRequest.HttpClientRequest, + parameters: ReadonlyArray, + getPath: () => string, + ) => Effect.suspend(() => { + const fail = (description: string, cause?: unknown) => Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.InvalidUrlError({ + request: method(""), + cause, + description, + }), + }), + ) + if (parameters.some((value) => value === "" || /^(?:\\.|%2e){1,2}$/i.test(value))) { + return fail("Path parameters must be non-empty and cannot be dot segments") + } + let path: string + try { + path = getPath() + } catch (cause) { + return fail("Failed to encode path parameter", cause) + } + if (path.split("/").some((segment) => /^(?:\\.|%2e){1,2}$/i.test(segment))) { + return fail("Request paths cannot contain dot segments") + } + return Effect.succeed(method(path)) + }) const decodeSuccess = (response: HttpClientResponse.HttpClientResponse) => response.json as Effect.Effect const decodeVoid = (_response: HttpClientResponse.HttpClientResponse) => @@ -1226,8 +1286,10 @@ export const make = ( } return { httpClient, - "getUser": (id, options) => HttpClientRequest.get(\`/users/\${id}\`).pipe( - onRequest(options?.config)(["2xx"]) + "getUser": (id, options) => __makePathRequest(HttpClientRequest.get, [id], () => "/users/" + __encodePathParam(id) + "").pipe( + Effect.flatMap((request) => request.pipe( + onRequest(options?.config)(["2xx"]) + )) ) } } @@ -1274,8 +1336,8 @@ export const TestClientError = ( `onRequest(options?.config)([], {"404":"DownloadArchive404"}, {"binary":["2xx"],"voidSuccess":[],"voidError":[]})`, `readonly "downloadArchive": (options: { readonly config?: Config | undefined } | undefined) => Effect.Effect`, `readonly "downloadAvatarStream": () => Stream.Stream`, - `"downloadAvatar": (options) => HttpClientRequest.get(\`/avatar\`).pipe( - onRequest(options?.config)([], undefined, {"binary":["2xx"],"voidSuccess":[],"voidError":[]})`, + `"downloadAvatar": (options) => HttpClientRequest.get("/avatar").pipe( + onRequest(options?.config)([], undefined, {"binary":["2xx"],"voidSuccess":[],"voidError":[]})`, `readonly "downloadCustomBinaryStream": () => Stream.Stream`, `import * as HttpClient from "effect/unstable/http/HttpClient"`, `onRequest(options?.config)([], {"400":"DownloadMixedContent400"}, {"binary":["2xx"],"voidSuccess":[],"voidError":[]})`, @@ -2771,6 +2833,38 @@ export const __HttpApiMultipartFiles = Multipart.FilesSchema`, }) describe("regression", () => { + it.effect("quotes static path text with and without parameters", () => { + const prefix = "/files/\"`\\\n/" + const suffix = "/content\"`" + const operation: OpenAPISpecOperation = { + operationId: "read", + parameters: [], + tags: ["Files"], + security: [], + responses: { "204": { description: "No content" } } + } + return assertRuntimeIncludes({ + openapi: "3.1.0", + info: { title: "Quoted paths", version: "1.0.0" }, + components: { schemas: {}, securitySchemes: {} }, + security: [], + tags: [], + paths: { + [prefix + suffix]: { get: operation }, + [prefix + "{id}" + suffix]: { + get: { + ...operation, + operationId: "readById", + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }] + } + } + } + }, [ + `HttpClientRequest.get(${JSON.stringify(prefix + suffix)})`, + `() => ${JSON.stringify(prefix)} + __encodePathParam(id) + ${JSON.stringify(suffix)}` + ]) + }) + it.effect("emits compilable clients when schema examples are invalid", () => assertGeneratedClientsCompile({ openapi: "3.0.3", diff --git a/repos/effect/packages/tools/openapi-generator/test/OpenApiTransformer.test.ts b/repos/effect/packages/tools/openapi-generator/test/OpenApiTransformer.test.ts index 135be0a08..6381a23e9 100644 --- a/repos/effect/packages/tools/openapi-generator/test/OpenApiTransformer.test.ts +++ b/repos/effect/packages/tools/openapi-generator/test/OpenApiTransformer.test.ts @@ -26,7 +26,12 @@ const modules = { type TransformClient = (client: HttpClient.HttpClient) => Effect.Effect -async function loadClient(source: string) { +async function loadClient< + Client = { + readStream: () => Stream.Stream + readSse: () => Stream.Stream + } +>(source: string) { const bundle = await rolldown({ input: "client.ts", external: (id) => id !== "client.ts", @@ -41,10 +46,7 @@ async function loadClient(source: string) { const compiled = output[0] assert.strictEqual(compiled.type, "chunk") const exports = {} as { - make: (httpClient: HttpClient.HttpClient, options?: { transformClient: TransformClient }) => { - readStream: () => Stream.Stream - readSse: () => Stream.Stream - } + make: (httpClient: HttpClient.HttpClient, options?: { transformClient: TransformClient }) => Client } new Function("require", "exports", compiled.code)( (id: keyof typeof modules) => { @@ -68,6 +70,98 @@ const paths = [ ] as const describe("OpenApiTransformer", () => { + describe("path parameters", () => { + const generate = Effect.gen(function*() { + const generator = yield* OpenApiGenerator.OpenApiGenerator + const source = yield* generator.generate({ + openapi: "3.1.0", + info: { title: "Files", version: "1.0.0" }, + components: { schemas: {}, securitySchemes: {} }, + security: [], + tags: [], + paths: { + "/files/{id}/content": { + get: { + operationId: "read", + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], + tags: ["Files"], + security: [], + responses: { + "200": { + description: "File contents", + content: { "application/octet-stream": { schema: { type: "string" } } } + } + } + } + } + } + }, { name: "TestClient", format: "httpclient-type-only" }) + return yield* Effect.promise(() => + loadClient<{ + read: (id: string) => Effect.Effect + readStream: (id: string) => Stream.Stream + }>(source) + ) + }).pipe(Effect.provide(OpenApiGenerator.layerTransformerTs)) + + it.effect("percent-encodes each parameter as one path segment", () => + Effect.gen(function*() { + const make = yield* generate + const urls: Array = [] + const client = make( + HttpClient.make((request, url) => { + urls.push(url.href) + return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(""))) + }).pipe(HttpClient.mapRequest(HttpClientRequest.prependUrl("https://example.com"))) + ) + for ( + const [value, encoded] of [ + ["alice", "alice"], + ["../a/b?c#d\\e%20", "..%2Fa%2Fb%3Fc%23d%5Ce%2520"], + ["hello world/é", "hello%20world%2F%C3%A9"] + ] + ) { + yield* client.read(value) + assert.strictEqual(urls.at(-1), `https://example.com/files/${encoded}/content`) + } + assert.strictEqual(urls.length, 3) + })) + + it.effect("rejects empty and dot parameters with a typed error before sending", () => + Effect.gen(function*() { + const make = yield* generate + let requests = 0 + const client = make( + HttpClient.make(() => { + requests++ + return Effect.die("Unexpected request") + }).pipe(HttpClient.mapRequest(HttpClientRequest.prependUrl("https://example.com"))) + ) + for (const value of ["", ".", ".."]) { + const error = yield* Effect.flip(client.read(value)) + assert.instanceOf(error, HttpClientError.HttpClientError) + assert.instanceOf(error.reason, HttpClientError.InvalidUrlError) + } + assert.strictEqual(requests, 0) + })) + + it.effect("propagates path rejection through the stream error channel", () => + Effect.gen(function*() { + const make = yield* generate + let requests = 0 + const client = make( + HttpClient.make(() => { + requests++ + return Effect.die("Unexpected request") + }).pipe(HttpClient.mapRequest(HttpClientRequest.prependUrl("https://example.com"))) + ) + const error = yield* Effect.flip(Stream.runCollect(client.readStream(".."))) + assert.instanceOf(error, HttpClientError.HttpClientError) + assert.instanceOf(error.reason, HttpClientError.InvalidUrlError) + assert.strictEqual(requests, 0) + })) + }) + for (const { encoding, format } of paths) { const binary = encoding === "binary" const body = binary diff --git a/repos/effect/packages/vitest/CHANGELOG.md b/repos/effect/packages/vitest/CHANGELOG.md index 912974125..9923f4667 100644 --- a/repos/effect/packages/vitest/CHANGELOG.md +++ b/repos/effect/packages/vitest/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/vitest +## 4.0.0-rc.115 + +### Patch Changes + +- Updated dependencies [[`657254b`](https://github.com/Effect-TS/effect/commit/657254b8218628b0116497d09aaf783cb88279f3), [`f9ef0e9`](https://github.com/Effect-TS/effect/commit/f9ef0e970260b5de143392d00593e6c8b4614cff), [`4f73f9e`](https://github.com/Effect-TS/effect/commit/4f73f9e0bd9538260f0f7166e0e6abdbd507a0e8)]: + - effect@4.0.0-rc.115 + ## 4.0.0-rc.114 ### Patch Changes diff --git a/repos/effect/packages/vitest/package.json b/repos/effect/packages/vitest/package.json index b838fc22f..c51b08b89 100644 --- a/repos/effect/packages/vitest/package.json +++ b/repos/effect/packages/vitest/package.json @@ -1,6 +1,6 @@ { "name": "@effect/vitest", - "version": "4.0.0-rc.114", + "version": "4.0.0-rc.115", "type": "module", "license": "MIT", "description": "A set of helpers for testing Effects with vitest", diff --git a/repos/effect/pnpm-lock.yaml b/repos/effect/pnpm-lock.yaml index ba273b14e..56ea58c72 100644 --- a/repos/effect/pnpm-lock.yaml +++ b/repos/effect/pnpm-lock.yaml @@ -349,8 +349,8 @@ importers: specifier: ^6.1.6 version: 6.1.6 valibot: - specifier: ^1.4.2 - version: 1.4.2(typescript@7.0.2) + specifier: ^1.5.0 + version: 1.5.0(typescript@7.0.2) packages/opentelemetry: dependencies: @@ -6321,8 +6321,8 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - valibot@1.4.2: - resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} + valibot@1.5.0: + resolution: {integrity: sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -12102,7 +12102,7 @@ snapshots: utils-merge@1.0.1: {} - valibot@1.4.2(typescript@7.0.2): + valibot@1.5.0(typescript@7.0.2): optionalDependencies: typescript: 7.0.2